diff --git a/README.adoc b/README.adoc index e36efe534bb..a8d8db5eb94 100644 --- a/README.adoc +++ b/README.adoc @@ -1,34 +1,45 @@ -= Address Book (Level 3) += iTrack Pro ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level3[image:https://travis-ci.org/se-edu/addressbook-level3.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level3[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level3?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level3/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level3?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level3&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] - +https://travis-ci.org/AY1920S2-CS2103-T09-2/main[image:https://travis-ci.org/AY1920S2-CS2103-T09-2/main.svg?branch=master[Build Status]] +https://app.netlify.com/sites/eager-kirch-5d6bd2/deploys[image:https://api.netlify.com/api/v1/badges/8179f1a7-7a48-48a7-98ac-85784b47997d/deploy-status[Netlify Status]] +https://coveralls.io/github/AY1920S2-CS2103-T09-2/main?branch=master[image:https://coveralls.io/repos/github/AY1920S2-CS2103-T09-2/main/badge.svg?branch=master[Coverage Status]] ifdef::env-github[] -image::docs/images/Ui.png[width="600"] +image::docs/images/Ui.png[width="600" align="center"] endif::[] ifndef::env-github[] -image::images/Ui.png[width="600"] +image::images/Ui.png[width="600" align="center"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. +* iTrack Pro is for the shop owners to keep track of the products, customers, and transactions of the shop and have a better understanding of the business by viewing the performance of products and behaviors of customers. +* It is also able to provide an analysis of the entire business to help the owner manage the shop. +* This is a desktop Java Application. It has a GUI (Graphic User interface). + +== Target Users +Shop owners who + +* Are selling products +** Cannot afford expensive management systems +** Are relying on manual work to record products +** Want to optimise sales based analysis of previous sales +** Have many products and a large inventory +* Prefer desktop apps over other types +* Can type fast +* Prefer typing over mouse input +* Are reasonably comfortable using CLI apps == Site Map * <> * <> -* <> * <> * <> == Acknowledgements +* The application is based on the https://github.com/nus-cs2103-AY1920S2/addressbook-level3[AddressBook-Level3] project created by https://se-education.org[SE-EDU initiative]. * Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. * Libraries used: https://openjfx.io/[JavaFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/junit-team/junit5[JUnit5] diff --git a/build.gradle b/build.gradle index 93029ef8262..811b0844c88 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,14 @@ dependencies { implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'linux' + + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'linux' + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' @@ -67,7 +75,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = '[CS2103-T09-2][iTrack Pro].jar' destinationDir = file("${buildDir}/jar/") } @@ -133,9 +141,9 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level3', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level3', - 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) + 'site-name': 'iTrack Pro', + 'site-githuburl': 'https://github.com/AY1920S2-CS2103-T09-2/main.git', + //'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) ] options['template_dirs'].each { diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 458e6134f45..c0bd1616210 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,35 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 3 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + +iTrack Pro was developed by the AY1920S2-CS2103-T09-2 team. {empty} + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== Tan Su Yee +image::aliciatxl.png[width="150", align="left"] +{empty}[https://github.com/aliciatxl[github]][<>] -Role: Project Advisor +Role: Project Advisor + +Responsibilities: Product features ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Tan Jun Hao +image::junhaotan.png[width="150", align="left"] +{empty}[http://github.com/junhaotan[github]][<>] Role: Team Lead + -Responsibilities: UI +Responsibilities: Customer features ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== He Yingxu +image::YingxuH.png[width="150", align="left"] +{empty}[http://github.com/YingxuH[github]][<>] Role: Developer + -Responsibilities: Data - -''' - -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] - -Role: Developer + -Responsibilities: Dev Ops + Threading - -''' - -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] - -Role: Developer + -Responsibilities: UI +Responsibilities: Transaction features ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 81be279ef6d..5813a5fda87 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,6 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level3/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/AY1920S2-CS2103-T09-2/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach us at `e0037186@u.nus.edu` diff --git a/docs/DevOps.adoc b/docs/DevOps.adoc index 2aa5a6bc0c1..9bdebff02bd 100644 --- a/docs/DevOps.adoc +++ b/docs/DevOps.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Dev Ops += iTrack Pro - Dev Ops :site-section: DeveloperGuide :toc: :toc-title: diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 3d65905a853..fee3c83759f 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Developer Guide += iTrack Pro - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -12,9 +12,9 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103-T09-2/main -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `iTrackPro Team`      Since: `Feb 2020`      Licence: `MIT` == Setting up @@ -30,10 +30,6 @@ image::ArchitectureDiagram.png[] The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick overview of each component. -[TIP] -The `.puml` files used to create diagrams in this document can be found in the link:{repoURL}/docs/diagrams/[diagrams] folder. -Refer to the <> to learn how to create and edit diagrams. - `Main` has two classes called link:{repoURL}/src/main/java/seedu/address/Main.java[`Main`] and link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp`]. It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. @@ -64,10 +60,10 @@ image::LogicClassDiagram.png[] [discrete] ==== How the architecture components interact with each other -The _Sequence Diagram_ below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The _Sequence Diagram_ below shows how the components interact with each other for the scenario where the user issues the command `deletep 1`. -.Component interactions for `delete 1` command -image::ArchitectureSequenceDiagram.png[] +.Component interactions for `deletep 1` command +image::ArchitectureSequenceDiagram.png[width="700"] The sections below give more details of each component. @@ -77,9 +73,9 @@ The sections below give more details of each component. .Structure of the UI Component image::UiClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +*API* : link:{repoURL}/blob/master/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `CustomerListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] @@ -96,17 +92,17 @@ The `UI` component, image::LogicClassDiagram.png[] *API* : -link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +link:{repoURL}/blob/master/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `InventorySystemParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person). +. The command execution can affect the `Model` (e.g. adding a customer). . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. . In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user. -Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. +Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("deletec 1")` API call. -.Interactions Inside the Logic Component for the `delete 1` Command +.Interactions Inside the Logic Component for the `deletec 1` Command image::DeleteSequenceDiagram.png[] NOTE: The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. @@ -117,32 +113,34 @@ NOTE: The lifeline for `DeleteCommandParser` should end at the destroy marker (X .Structure of the Model Component image::ModelClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +*API* : link:{repoURL}/blob/master/src/main/java/seedu/address/model/Model.java[`Model.java`] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the Inventory System data. +* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. [NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + +As a more OOP model, we can store a `Tag` list in `Inventory System`, which `Customer` can reference. This would allow `Inventory System` to only require one `Tag` object per unique `Tag`, instead of each `Customer` needing their own `Tag` object. An example of how such a model may look like is given below. + + -image:BetterModelClassDiagram.png[] +image:BetterModelClassDiagram.png[width="600"] [[Design-Storage]] === Storage component .Structure of the Storage Component -image::StorageClassDiagram.png[] +image::StorageClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +*API* : link:{repoURL}/blob/master/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in json format and read it back. +* can save the Inventory System data in json format and read it back. [[Design-Commons]] === Common classes @@ -153,94 +151,278 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. -// tag::undoredo[] -=== [Proposed] Undo/Redo feature -==== Proposed Implementation +// tag::manageitem[] +=== Add/delete/edit/clear/list/find <>/<> feature +The manage product mechanism is facilitated by `InventorySystemParser`. +First, the InventorySystemParser class parses the user command. +This results in a Command object which is executed by the LogicManager. +The command execution modifies Model's customer list depending on the command. +The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui. + +The following commands are available to be parsed by InventorySystemParser: -The undo/redo mechanism is facilitated by `VersionedAddressBook`. -It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. -Additionally, it implements the following operations: +* `AddCustomerCommand/AddProductCommand` +- Adds the customer/product into the list in the inventory system. +* `ListCustomerCommand/ListProductCommand` +- Lists all customer/product in the list. +* `ClearCustomerCommand/ClearProductCommand` +- Clears all customer/product in the list. +* `DeleteCustomerCommand/DeleteProductCommand` +- Deletes the selected customer/product from the list in the inventory system. +* `EditCustomerCommand/EditProductCommand` +- Edits the customer/product details. +* `FindCustomerCommand/FindProductCommand` +- Finds customer/product with attributes containing given keyword(s). -* `VersionedAddressBook#commit()` -- Saves the current address book state in its history. -* `VersionedAddressBook#undo()` -- Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` -- Restores a previously undone address book state from its history. +The commands all inherit from superclass `Command`. Only add, delete, edit and find commands require a command parser to parse the arguments entered by the user. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +The following sequence diagram shows how the add operation works: -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +image::AddItemSequenceDiagram.png[] -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +The following sequence diagram shows how the list operation works: -image::UndoRedoState0.png[] +image::ListItemSequenceDiagram.png[] -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +The following sequence diagram shows how the edit operation works: -image::UndoRedoState1.png[] +image::EditItemSequenceDiagram.png[] -Step 3. The user executes `add n/David ...` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +The following activity diagram summarizes what happens when a user executes a command that changes the customer/product list in the model: -image::UndoRedoState2.png[] +image::CommandActivityDiagram.png[] +==== Design Considerations + +===== Aspect: How commands are executed + +* **Alternative 1 (current choice):** Keep customers/products in the inventory system +** Pros: Easy to implement, use less memory +** Cons: If inventory system is corrupted then the data is lost +* **Alternative 2:** Keep customers/products in individual management systems, separate each of the classes +** Pros: Keep implementation of products, customers and transactions separate +** Cons: Use more memory, tedious implementation + +===== Aspect: Data structure to support the commands + +* **Alternative 1 (current choice):** Use a list (`ArrayList`) to store the customers/products +** Pros: Easy to sort and iterate through the list, get(index) method always gives an O(1) performance +** Cons: allows duplicates +* **Alternative 2:** Use a hashmap to store the customers/products +** Pros: Find can be done in O(1), does not allow duplicate keys +** Cons: Difficult to sort and iterate through hashmap, get(key) can be O(1) in the best case and O(n) in the worst case. +// end::manageitem[] + +// tag::managetransaction[] +=== Add/edit/undo/list/find <> +The user input is handled by the `MainWindow` class in Ui first, then passed to the `LogicManager` and parsed into +`AddTransactionCommand`, `EditTransactionCommand`, etc. Depending on the nature of each command, new transaction or +updated transaction will be added to a new index or existing index of the `UniqueTransactionList`, hosted by the +`InventorySystem` class. For the `deleteTransactionCommand`, a transaction will be dropped from the `internalList`. +Since the `quantity` and `sales` attribute will affect the same attributes of a product, the affiliated `product` will +also be edited. In the end, the `filteredTransactionList` of the system will be updated so that the user can view the change +accordingly. For the list and find transction commands, the filteredTransactionList will be updated for the UI to interact with users. +One command is implemented for each operations in the logic module: + +* `AddTransactionCommand` -- Adds a transaction into the system and update the the quantity and sales attribute +of the corresponding product. +* `EditTransactionCommand` -- Edit details of a transaction. If `quantity` is changed, edit the affected product +as well. +* `UndoTransactionCommand` -- Undo a transaction from the system and edit the affiliated product. +* `ListTransactionCommand` -- List all the transaction in the system. +* `FindTransactionCommand` -- Find certain transactions by keywords. + +For each command, a parser is implemented to parse the input into arguments. + +* `AddTransactionCommmandParser` -- Parse the add transaction input and generates `AddTransactionCommand`. + +* `EditTransactionCommandParser` -- Parse the edit transaction input and generates `EditTransactionCommand`. + +* `UndoTransactionCommandParser` --Parse the undo transaction input and generates `UndoTransactionCommand`. + +* `ListTransactionCommandParser` --Parse the list transaction input and generates `ListTransactionCommand`. + +* `FindTransactionCommandParser` --Parse the find transaction input and generates `FindTransactionCommand`. + + +The following sequence diagram shows how each operation works. + +AddTransaction Operation: + +image::AddTransactionSequenceDiagram.png[] [NOTE] -If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +The AddTransactionCommmandParser returns a transactionFacotry with `productIndex` and `customerIndex`, while a +transaction is only generated in AddTransactionCommand. -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +EditTransaction Operation: -image::UndoRedoState3.png[] +Unlike the edit operation of customer and products, editting transaction will trigger another operation of +editting its associated product with the new quantity and money. If its product is editted, its quantity will be added back +to the original product's quantity, and the new quantity will be deducted from the quantity of the newly referenced product. The update on sales object will +be done in the reverse way. In the end, the new transction and product will replace the old ones in the system. -[NOTE] -If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. +image::EditTransactionSequenceDiagram.png[] -The following sequence diagram shows how the undo operation works: +UndoTransaction Operation: -image::UndoSequenceDiagram.png[] +Undoing a transaction will add back its quantity to teh associated product and remove its amount of money from the +sales of that product. -NOTE: The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +image::UndoTransactionSequenceDiagram.png[] -The `redo` command does the opposite -- it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +ListTransaction/FindTransaction Operation: -[NOTE] -If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +The list operation for transaction is the same as that for products and customers. + +==== Design Considerations -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +===== Aspect: How to store product & customer in transaction. + +* **Alternative 1 (current choice):** Store an unique id and name of the product/ customer. +** Pros: Do not need to update transaction while product is editted. +** Cons: More complex when displaying the customer/product information in UI. Needs to query model whenver +the system needs to calcualte statistics related with product and transactions. +* **Alternative 2 (previous choice):** Store the product/ customer instance as an attribute. +** Pros: Easy to construct a transaction and display product/ customer name. +** Cons: Easy to generate bugs while any of the instance is editted. Needs to update the product in transaction when a product +is editted. + +===== Aspect: How to change the quantity & sales attribute of product while editing transactions. + +* **Alternative 1 (current choice):** If quantity/ product is changed, check validation first, +re-store the quantity & sales of the original product, and then +and update the quantity & sales of the new product. +** Pros: Straightforward logic, not likely to create bugs. +** Cons: Validation checking would be very complex. + +* **Alternative 2 (previous choice):** If quantity/ product is changed, +re-store the quantity & sales of the original product, check validation +(whether the product has that much inventory as required on transaction), and then +and update the quantity & sales of the new product. +** Pros: Easy to implement. +** Cons: Likely to generate bugs when the new quantity exceeds inventory, i.e. the edit operation is not valid. +// end::managetransaction[] -image::UndoRedoState4.png[] +// tag::getprofitrevenue[] +=== Get <>/<> feature +The manage product mechanism is facilitated by `InventorySystemParser`. +First, the InventorySystemParser class parses the user command. +Then the `RevenueCommandParser/ProfitCommandParser` parses the user input arguments into Command objects. +The resulting `RevenueCommand/ProfitCommand` is executed by the LogicManager. +The command execution calculates the revenue/profit depending on the command. +The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui. -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. +The following commands are available to be parsed by InventorySystemParser: -image::UndoRedoState5.png[] +* `RevenueCommand` +- Gets the revenue made in a selected period. +* `ProfitCommand` +- Gets the profit made in a selected period. -The following activity diagram summarizes what happens when a user executes a new command: +The commands all inherit from superclass `Command` and require command parsers to parse the user input arguments. -image::CommitActivityDiagram.png[] +The following sequence diagram shows how the profit operation works: + +image::ProfitSequenceDiagram.png[] +The revenue operation works in the same way as the profit operation. ==== Design Considerations -===== Aspect: How undo & redo executes +===== Aspect: How commands are executed -* **Alternative 1 (current choice):** Saves the entire address book. -** Pros: Easy to implement. -** Cons: May have performance issues in terms of memory usage. -* **Alternative 2:** Individual command knows how to undo/redo by itself. -** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). -** Cons: We must ensure that the implementation of each individual command are correct. +* **Alternative 1 (current choice):** Calculate revenue/profit only when needed +** Pros: Decreases coupling, easy to test +** Cons: Need to keep calculating if command is frequently used (increase time complexity especially if transaction list is long) +* **Alternative 2:** Keep track of revenue/profit in a singleton class +** Pros: No need to calculate when revenue/profit command is executed (instead, revenue/profit is updated whenever a transaction is added or edited) +** Cons: Hard to unit test, increase coupling +// end::getprofitrevenue[] + +//tag::plotsalesfeature[] +=== Plot the quantity sold of a product +The plot sales command is facilitated by `InventorySystemParser`. +First, the InventorySystemParser class parses the user command. +Then the `PlotSalesCommandParser` parses the user input arguments into the index of the product, +the `start date`, and the `end date`. +The generated `PlotSalesCommand` is executed by the LogicManager. +The command execution generates a daily time sequence and calcualte the quantity sold on each day by querying all the +related transactions. The time series data and the signal of displaying the sales graph is then encapsulated +as a CommandResult object which is passed back to the Ui. + +The following sequnce diagram shos how the plot sale operation works: + +image:PlotSalesSequenceDiagram.png[] +//end::plotsalesfeature[] + +//end::lowlimitfeature[] + +//tag::lowlimitfeature[] +=== Set low limit threshold to receive notifications for products +The low limit mechanism is facilitated by `InventorySystemParser`. +First, the InventorySystemParser class parses the user command. +Then the `LowLimitCommandParser` parses the user input arguments into `LowLimitCommand` object. +The resulting `LowLimitCommand` is executed by the LogicManager. +The command execution sets the updated threshold for the chosen product. +The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui. + +The command inherit from superclass `Command` and require command parser to parse the user input arguments. + +The following sequence diagram shows how the low limit operation works: + +image:LowLimitCommandSequenceDiagram.png[] + +==== Design Considerations + +===== Aspect: How the threshold field is being updated. + +* **Alternative 1 (current choice):** Separate low limit threshold command with edit product command +** Pros: Decreases coupling, easier to test. +** Cons: Additional command to edit the field of an product. +* **Alternative 2:** Integrate with existing edit product command +** Pros: Will reduce number of commands the user needs to use. +** Cons: Hard to unit test, increase coupling. +//end::lowlimitfeature[] + +//tag::notificationWindow[] +=== Notification window for products +The notification window appears whenever an product's quantity reaches the threshold that was set. +It comes with information such as the product's description and remaining quantity left. + +The following activity diagram shows how the operation works: + +image:NotificationWindowActivityDiagram.png[] + +==== Design Considerations + +===== Aspect: Display of the notification window + +* **Alternative 1 (current choice):** Only display product's name and remaining quantity. +** Pros: User friendly, easy to implement. +** Cons: Have to find and change the product's quantity via the command line. +* **Alternative 2:** Provide quick way to change product's quantity via GUI +** Pros: Speeds up the process of stocking up. +** Cons: Hard to do testing for GUI, and project scope was on command line application. +//end::notificationWindow[] + +//tag::sortProductList[] +=== Sort Product List +The product list sorts by the progress bar indicator beside each product's description whenever user enters `listp` command. + + +An example is shown below: + +image:SortingExample.png[] -===== Aspect: Data structure to support the undo/redo commands +The following activity diagram shows how the operation works: -* **Alternative 1 (current choice):** Use a list to store the history of address book states. -** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`. -* **Alternative 2:** Use `HistoryManager` for undo/redo -** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. -** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. -// end::undoredo[] +image:SortProductListActivityDiagram.png[] -// tag::dataencryption[] -=== [Proposed] Data Encryption +==== Design Considerations -_{Explain here how the data encryption feature will be implemented}_ +===== Aspect: When to sort the product list -// end::dataencryption[] +* **Alternative 1 (current choice):** Sorts only when user uses `listp` function. +** Pros: More intuitive and user friendly. +** Cons: Might be hard to track products' remaining balance without listing all the products. +* **Alternative 2:** Sorts whenever the product list is updated. +** Pros: Easy to track products' remaining balance. +** Cons: Might be confusing for user as the indexes for product will change as the product list is sorted automatically, +resulting in user to recheck products' index before entering in commands. +//end::sortProductList[] === Logging @@ -277,13 +459,17 @@ Refer to the guide <>. [appendix] == Product Scope -*Target user profile*: +*Target user profile*: Shop owners who -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +* Are selling products +** Cannot afford expensive management systems +** Are relying on manual work to record products +** Want to optimise sales based analysis of previous sales +** Have many products and a large inventory +* Prefer desktop apps over other types +* Can type fast +* Prefer typing over mouse input +* Are reasonably comfortable using CLI apps *Value proposition*: manage contacts faster than a typical mouse/GUI driven app @@ -295,35 +481,101 @@ Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (un [width="59%",cols="22%,<23%,<25%,<30%",options="header",] |======================================================================= |Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App +|`* * *` |new user |see usage instructions |refer to it when I forgot how to use the app -|`* * *` |user |add a new person | +|`* * *` |user |add <> to the system | -|`* * *` |user |delete a person |remove entries that I no longer need +|`* * *` |user |delete my <> from the system | -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +|`* * *` |user | edit my <> details in the system |keep the list updated -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +|`* * *` |user |view all <> in my shop |keep track of my <> -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +|`* *` |user |find <> by keyword or attributes |search <> more efficiently + +|`* *` |user |receive notifications when the <> quantity is running low |stock up for the particular <> + +|`* *` |user |set the inventory quantity low limit |get notified when my stock is running low + +|`* *` |analytical user |view the top-selling <> and worst-selling <> at one glance (e.g. dashboard that displays name of <>) | + +|`* *` |analytical user |view the sales or quantity of each individual <> in graphical format | so that it is easier to visualise + +|`* *` |analytical user |see predicted sales for the next month based on past sales |know which <> to stock up on + +|`* *` |analytical user |view a list of <> sorted by the amount of profits | + +|`* *` |analytical user |view the revenue on a daily/ monthly/ yearly basis or in a customised period | -_{More to be added}_ +|`*` |lazy user |keep track of previous inputs |enter/edit previous commands easily + +|`*` |lazy user |access <> that are running low in quantity easily |restock and update the system much faster + +|======================================================================= [appendix] == Use Cases -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +(For all use cases below, the *System* is the `iTrack Pro` and the *Actor* is the `user`, unless specified otherwise) + +[discrete] +=== Use case: UC01 - Delete an <> + +*MSS* + +1. User requests to pass:[list items (UC05)] +2. The app displays a list of requested items. +3. User requests to delete a specific item in the list +4. The item is deleted from the list + ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given index is invalid. ++ +[none] +** 3a1. The app shows an error message. ++ +Use case resumes at step 2. + +[discrete] +=== Use case: UC02 - Add an <> + +*MSS* + +1. User adds an item. +2. The app notifies the user that the item is added. + ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The add command is invalid or incomplete. +[none] +** 1a1. The app shows an error message. +** 1a2. The app shows an example of valid input for the command. ++ +Use case ends. [discrete] -=== Use case: Delete person +=== Use case: UC03 - Edit an <> *MSS* -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to pass:[list items (UC05)] +2. The app displays a list of requested items. +3. User requests to edit a specific item in the list. +4. The item is updated with the new information entered by the user. + + Use case ends. @@ -337,20 +589,175 @@ Use case ends. * 3a. The given index is invalid. + [none] -** 3a1. AddressBook shows an error message. +** 3a1. The app shows an error message. + Use case resumes at step 2. -_{More to be added}_ +* 3b. The edit command input is invalid or incomplete. ++ +[none] +** 3b1. The app shows an error message. +** 3b2. The app shows an example of valid input for the command. ++ +Use case resumes at step 3. + +[discrete] +=== Use case: UC04 - Find <> + +*MSS* + +1. User requests to find items by keyword and/or attribute. +2. The app displays the search result. + ++ +Use case ends. + +*Extensions* + +[none] +* 1a. No item was found with the keyword and/or attribute entered. ++ +[none] +** 1a1. The app shows a prompt message that no such item was found. ++ +Use case ends. + +[none] +* 1b. The find command input is invalid or incomplete. ++ +[none] +** 1b1. The app shows an error message. +** 1b2. The app shows an example of valid input for the command. ++ +Use case resumes at step 1. + +[discrete] +=== Use case: UC05 - List <> + +*MSS* + +1. User requests to list items. +2. The app displays a list of requested items. + ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +[none] +** 2a1. The app shows a prompt message that the list is empty. ++ +Use case ends. + +[discrete] +=== Use case: UC06 - View statistics (profit, revenue) + +*MSS* + +1. User requests to view statistics. +2. The app shows all statistics. + ++ +Use case ends. + +*Extensions* + +[none] +* 2a. There are no products, hence no statistics can be shown. ++ +[none] +** 2a1. The app shows an error message. ++ +Use case ends. + +[discrete] +=== Use case: UC07 - Predict sales + +*MSS* + +1. User requests to predict sales for next month. +2. The app shows the predictions. + ++ +Use case ends. + +*Extensions* + +[none] +* 2a. There are no products, hence no predictions can be made. ++ +[none] +** 2a1. The app shows an error message. ++ +Use case ends. + +[discrete] +=== Use case: UC08 - Configure low inventory notification settings (for products) + +*MSS* + +1. User sets the quantity threshold for a particular product. + ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The input limit is invalid or out of range or the product index is invalid. ++ +[none] +** 1a1. The app shows an error message. ++ +Use case ends. + +[discrete] +=== Use case: UC09 - Plot the quantity sold of a product (for products) + +*MSS* + +1. User enters the index of the product as well as the start date and end date for ploting purposes. + ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The index is out of boundary. ++ +[none] +** 1a1. The app shows an error message. ++ +Use case ends. + [appendix] == Non Functional Requirements -. Should work on any <> as long as it has Java `11` or above installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +. Should work on any <> as long as it has Java 11 or above installed. +. Should be able to hold up to 1000 customers without a noticeable sluggishness in performance for typical usage. . A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +. The system should work on Windows, Linux or Mac operating aystems. +. The system should be usable by a novice who has never used an inventory management system. +. The system should save the <>, <> and <> data permanently. +. There should be at least one <> and <> before a <> can be made. +. The response to any use action should become visible within 5 seconds. +. The system should be able to have up to 1000000 <>, 1000000 <> and 1000000 <>. +. The product (price, cost price, quantity, sales), transaction (quantity, money) fields and quantity threshold should be able to take integers up to 1000000. +. The price and cost price should be at least $1. +. The customer's address field should take up to 45 characters. +. The customer's name field should take up to 30 characters. +. The customer's phone field should be between 3 to 15 characters long and contain only integers. +. The customer's email field should take up to 40 characters and should be a valid email format. +. A customer can have up to 5 tags where each tag is up to 15 characters long. +. The user interface should be intuitive enough for users who are not IT-savvy. +. The source code should be open source. +. The product is offered as a free downloadable <> file. -_{More to be added}_ [appendix] == Glossary @@ -358,25 +765,39 @@ _{More to be added}_ [[mainstream-os]] Mainstream OS:: Windows, Linux, Unix, OS-X -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +[[item]] Item:: +Any item belonging to either customer, product or a transaction class -[appendix] -== Product Survey +[[customer]] Customer:: +A customer that buys the user’s shop + +[[product]] Product:: +An item that is sold in the user’s shop -*Product Name* +[[transaction]] Transaction:: +A deal between a customer and the user’s shop that is made on a product -Author: ... +[[inventory]] Inventory:: +The products in stock. -Pros: +[[revenue]] Revenue:: +The sales of the product, calculated by adding up the transaction amounts of a particular product. -* ... -* ... +[[revenue]] Profit:: +The money gained from the sale of the product minus cost of product, calculated by revenue - total cost. -Cons: +[[sales]] Sales:: +Used interchangeably with revenue + +[[price]] Price:: +Selling price of the product + +[[cost]] Cost price:: +Price of making/ buying the product to sell + +[[jar]] Jar File:: +A https://en.wikipedia.org/wiki/JAR_(file_format)[JAR] (Java ARchive) is a package file format typically used to aggregate many Java class files and associated metadata and resources (text, images, etc.) into one file for distribution. -* ... -* ... [appendix] == Instructions for Manual Testing @@ -386,40 +807,305 @@ Given below are instructions to test the app manually. [NOTE] These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. -=== Launch and Shutdown +=== General + +==== Launch and Shutdown . Initial launch .. Download the jar file and copy into an empty folder .. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + Expected: Shows the GUI with a set of sample inventory system data. The window size may not be optimum. + +=== Customer + +==== Adding a customer + +. Adding a customer + +.. Test case: `addc n/John p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 t/friends t/owesMoney` + +Expected: Customer with given attributes is added to list. Details of the added customer shown in the status message. +.. Prerequisites: the following command already has been executed once already + +Test case: `addc n/John p/98765432` + +Expected: No customer is added as duplicate customers are not allowed. Error details shown in the status message. +.. Test case: `addc n/Jane p/98765432 e/jane@example.com a/31 michigan t/friends t/owesMoney t/iphone t/comingThurs t/regular t/giveDiscount` + +Expected: No customer is added as number of tags should be at most five. Error details shown in the status message. +.. Test case: `addc n/Bob n/Eddy p/98765432 e/bob@example.com a/323, Tampines Ave 3, #12-21 t/colleagues` + +Expected: No customer is added as there are multiple name prefixes. Error details shown in the status message. +.. Other incorrect add commands to try: `addc n/Jill`, `addc n/Jill p/0 e/jill@mail.com a/31 michigan` + +Expected: Similar to previous. -. Saving window preferences +==== Deleting a customer -.. Resize the window to an optimum size. Move the window to a different location. Close the window. -.. Re-launch the app by double-clicking the jar file. + - Expected: The most recent window size and location is retained. +. Deleting a customer while all customers are listed -_{ more test cases ... }_ +.. Prerequisites: List all customers using the `listc` command. Multiple customers in the list. +.. Test case: `deletec 1` + +Expected: First customer is deleted from the list. Details of the deleted customer shown in the status message. +.. Test case: `deletec 0` + +Expected: No customer is deleted. Error details shown in the status message. +.. Other incorrect delete commands to try: `deletec`, `deletec x` (where x is larger than the list size) + +Expected: Similar to previous. -=== Deleting a person +==== Editing a customer -. Deleting a person while all persons are listed +. Editing a customer while all customers are listed -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. -.. Test case: `delete 1` + - Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. -.. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + +.. Prerequisites: List all customers using the `listc` command. Multiple customers in the list. +.. Test case: `editc 1 n/Bob` + +Expected: First customer is edited from the list. Details of the edited customer shown in the status message. +.. Test case: `editc 1 e/0` + +Expected: No customers is edited as email is invalid. Error details shown in the status message. +.. Test case: `editc 1 n/Johnathan n/Alex` + +Expected: No customers is edited as there are multiple name prefixes. Error details shown in the status message. +.. Other incorrect edit commands to try: `editc 1`, `editc x` (where x is larger than the list size) + +Expected: Similar to previous. + +==== Clearing all customers + +. Clearing all customers + +.. Test case: `clearc` + +Expected: All customers are cleared. Details of success message is shown in the status bar. + +==== Listing all customers + +. Listing all customers + +.. Test case: `listc` + +Expected: All customers are listed. Details of success message is shown in the status bar. + +==== Finding customers + +. Finding customers + +.. Test case: `findc n/alice` + +Expected: All customers with full word 'alice' in name are listed. Details of success message is shown in the status bar. +.. Test case: `findc n/alex alice` + +Expected: All customers with full word 'alex' **OR** 'alice' in name are listed. Details of success message is shown in the status bar. +.. Test case: `findc a/serangoon yishun` + +Expected: All customers with full words matching 'serangoon' **OR** 'yishun' in their addresses are listed. Details of success message is shown in the status bar. +.. Test case: `findc a/serangoon n/bob` + +Expected: All customers with full words matching 'serangoon' in their addresses **AND** 'bob' in their names are listed. Details of success is shown in the status bar. +.. Test case: `findc n/alex n/peter` + +Expected: No customers found as there are multiple name prefixes. Error details shown in the status message. + +=== Product + +==== Adding a product + +. Adding a product + +.. Test case: `addp d/iphone cp/400 pr/1000 q/10 s/100` + + Expected: Product with given attributes is added to list. Details of the added product shown in the status message. +.. Prerequisites: the following command already has been executed once already + + Test case: `addp d/iphone cp/400 pr/1000 q/10 s/100` + + Expected: No product is added as duplicate products are not allowed. Error details shown in the status message. +.. Test case: `addp d/iphone cp/400.5 pr/1000 q/10` + + Expected: No product is added as cost price should be an integer. Error details shown in the status message. +.. Test case : `addp d/iphone d/ipad cp/400 pr/1000 q/10` + + Expected: No product is added as there are multiple description prefixes. Error details shown in the status message. +.. Other incorrect add commands to try: `addp d/iphone cp/40 pr/1000 q/x` (where x is larger than 1000000), `addp d/iphone cp/40 q/100000` + Expected: Similar to previous. -_{ more test cases ... }_ +==== Deleting a product + +. Deleting a product while all products are listed + +.. Prerequisites: List all products using the `listp` command. Multiple products in the list. +.. Test case: `deletep 1` + +Expected: First product is deleted from the list. Details of the deleted product shown in the status message. +.. Test case: `deletep 0` + +Expected: No product is deleted. Error details shown in the status message. +.. Other incorrect delete commands to try: `deletep`, `deletep x` (where x is larger than the list size) + +Expected: Similar to previous. + +==== Editing a product + +. Editing a product while all products are listed + +.. Prerequisites: List all products using the `listp` command. Multiple products in the list. +.. Test case: `editp 1 d/Bag` + +Expected: First product is edited from the list. Details of the edited product shown in the status message. +.. Test case: `editp 1 pr/0` + +Expected: No product is edited as price is invalid. Error details shown in the status message. +.. Test case: `editp 1 pr/300 pr/500` + +Expected: No product is edited as there are multiple price prefixes. Error details shown in the status message. +.. Other incorrect edit commands to try: `editp 1`, `editp x` (where x is larger than the list size) + +Expected: Similar to previous. + +==== Clearing all products + +. Clearing all products + +.. Test case: `clearp` + +Expected: All products are cleared. Details of success is shown in the status bar. + +==== Listing all products + +. Listing all products + +.. Test case: `listp` + +Expected: All products are listed. Details of success is shown in the status bar. + +==== Finding products + +. Finding products + +.. Test case: `findp bag` + +Expected: All products with full word 'bag' in description are listed. Details of success is shown in the status bar. +.. Test case: `findp bag yellow` + +Expected: All products with full words matching 'bag' or 'yellow' in description are listed. Details of success is shown in the status bar. + +=== Transaction + +==== Adding a transaction + +. Adding a transaction + +.. Test case: `addt c/1 p/1 q/1 dt/2020-04-11 11:44 m/30 d/under discount` + +Expected: Transaction with given attributes is added to list. Details of the added transaction shown in the status message. +.. Prerequisites: the following command already has been executed once already + +Test case: `addt c/1 p/1 q/1 dt/2020-04-11 10:44 m/30 d/under discount` + +Expected: No transaction is added as duplicate transactions are not allowed. Error details shown in the status message. +.. Test case: `addt c/1 p/1 q/0` + +Expected: No transaction is added as quantity should be a positive integer. Error details shown in the status message. +.. Test case: `addt c/1 c/1 p/1 q/10` + +Expected: No transaction is added as there are multiple customer prefixes. Error details shown in the status message. +.. Other incorrect add commands to try: `addt c/x p/1 q/2` (where x is larger than size of customer list), `addt c/1 p/1` + +Expected: Similar to previous. + +==== Undoing a transaction + +. Undoing a transaction while all transactions are listed + +.. Prerequisites: List all transactions using the `listt` command. Multiple transactions in the list. +.. Test case: `undot 1` + +Expected: First transaction in the list is undone. Details of the undone transaction shown in the status message. +.. Test case: `undot 0` + +Expected: No transaction is deleted. Error details shown in the status message. +.. Other incorrect undo commands to try: `undot`, `undot x` (where x is larger than the list size) + +Expected: Similar to previous. + +==== Editing a transaction + +. Editing a transaction while all transactions are listed + +.. Prerequisites: List all transactions using the `listt` command. Multiple transactions in the list. +.. Test case: `editt 1 d/Discount` + +Expected: First transaction is edited from the list. Details of the edited transaction shown in the status message. +.. Test case: `editt 1 m/0` + +Expected: No transaction is edited as amount is invalid. Error details shown in the status message. +.. Test case: `editt 1 d/Discount d/offer` + +Expected: No transaction is edited as there are multiple description prefixes. Error details shown in the status message. +.. Other incorrect edit commands to try: `editt 1`, `editt x` (where x is larger than the list size) + +Expected: Similar to previous. + +==== Clearing all transactions + +. Clearing all transactions + +.. Test case: `cleart` + +Expected: All transactions are cleared. Details of success is shown in the status bar. + +==== Listing all transactions + +. Listing all transactions + +.. Test case: `listt` + +Expected: All transactions are listed. Details of success is shown in the status bar. + +==== Finding transactions + +. Finding transactions + +.. Test case: `findt c/alice` + +Expected: All transactions with full word 'alice' in their customer names are listed. Details of success is shown in the status bar. +.. Test case: `findt p/bag watch` + +Expected: All transactions with full words matching 'bag' **OR** 'watch' in their product names are listed. Details of success is shown in the status bar. +.. Test case: `findt c/alice m/10` + +Expected: All transactions with full words matching 'alice' in their customer names **AND** have transaction amounts of 10 are listed. Details of success is shown in the status bar. +.. Test case: `findt c/alice c/john` + +Expected: No transaction found as there are multiple customer prefixes. Error details shown in the status message. + +=== Statistics + +==== Getting the revenue made in a certain period + +. Getting the revenue made in a certain period + +.. Prerequisites: There is at least one product present. +.. Test case: `revenue sd/2020-01-01 10:00 ed/2020-12-12 10:01` + +Expected: The calculated revenue is shown in the status message. +.. Test case: `revenue sd/2020-09-01 10:00 ed/2020-01-12 10:01` + +Expected: Revenue cannot be calculated if start date is after end date. Error details shown in the status message. +.. Other incorrect revenue commands to try: `revenue sd/2020-01-01 10:00 ed/2020-01-12`, `revenue` + +Expected: Similar to previous. + +==== Getting the profit made in a certain period + +. Getting the profit made in a certain period + +.. Prerequisites: There is at least one product present. +.. Test case: `profit sd/2020-01-01 10:00 ed/2020-12-12 10:01` + +Expected: The calculated profit is shown in the status message. +.. Test case: `profit sd/2020-09-01 10:00 ed/2020-01-12 10:01` + +Expected: Profit cannot be calculated if start date is after end date. Error details shown in the status message. +.. Other incorrect profit commands to try: `profit sd/2020-01-01 10:00 ed/2020-01-12`, `profit` + +Expected: Similar to previous. + +==== Setting the low-inventory threshold + +. Setting the low-inventory threshold + +.. Prerequisites: There is at least one product present. +.. Test case: `lowlimit p/1 t/20` + +Expected: The calculated profit is shown in the status message. +.. Test case: `lowlimit p/1 t/0` + +Expected: Current threshold of product is not modified as threshold can only take positive integer values. Error details shown in the status message. +.. Other incorrect lowlimit commands to try: `lowlimit p/x t/20` (where x is more than product list size), `lowlimit` + +Expected: Similar to previous. + +==== Predicting the sales for the next month + +. Predicting the sales for the next month + +.. Prerequisites: There is at least one product present. +.. Test case: `predict` + +Expected: The predicted revenue for next month is shown in the status message. + +==== Plotting sales of a product + +. Plotting sales of a product + +.. Prerequisites: List all products using the `listp` command. Multiple products in the list. +.. Test case: `plotsales 1 sd/2020-03-20 10:00 ed/2020-03-30 10:00` + +Expected: The sales of the product is plotted in a graph in a new window that pops up. +.. Test case: `plotsales 1 sd/2020-03-20 10:00 ed/2020-01-30 10:00` + +Expected: Sales of product is not plotted as start date is after end date. Error details shown in the status message. +.. Other incorrect plotsales commands to try: `plotsales x sd/2020-03-20 10:00 ed/2020-03-30 10:00` (where x is more than product list size), `plotsales` + +Expected: Similar to previous. + +[appendix] +== Effort + +Throughout this journey, we have overcame many challenges and gained much more experience in software engineering. +Our project was ambitious as it consisted of multiple entities. In addition to the original address book with the persons entity, we also included products and transactions to the system. +Since the structure was quite complex, we had to brainstorm about the best way to incorporate products and transactions into the app. -=== Saving data +The person class had to be refactored to fit our user profile of customers. +The commands for products were similar to the original commands for person, however there were also some differences since the requirements for products and persons varied. +For example, the attributes required for products were vastly different for customers, and new restrictions needed to be applied. +The commands for transactions were quite different from both customers and products, since transaction is an association class between customer and product. +For example, the add transaction command affected the product quantity and sales, so it had to update the product too, whereas for customer and product there was no need to modify other classes. +We also needed to consider the proper way to keep track of customers and products to ensure that there were no duplicates. Initially we tried keeping track using only the attributes however there were bugs so we added customer and product ids. -. Dealing with missing/corrupted data files +With multiple entities, a lot of thought went into figuring out how to display all the data. +Throughout the process, we revamped the ui such that all the information would be presented clearly. Ultimately, our design differed from the original as we settled on a tab design. +Initially, there were different windows showing different information too. In the end, we combined most of the information into a single window, and split the information shown according to categories which were listed as tabs. +Besides the layout of the ui, the design also took time to finalise as we wanted to make our app look professional and modern. -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +Lastly, the statistics were quite challenging to implement. This is especially so as we used various bars and graphs, which were new to us and we had to learn independently. +The calculations were relatively easier as it was just simple math calculations of profit etc. -_{ more test cases ... }_ +In all, even though our project was complex, we managed to implement most of our features and even included extra features that we had not plan on intially. Given the moderate to high difficulty level of the project, we are glad that we achieved our project goals. diff --git a/docs/Documentation.adoc b/docs/Documentation.adoc index ad90ac87bda..b5131a979c1 100644 --- a/docs/Documentation.adoc +++ b/docs/Documentation.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Documentation += iTrack Pro - Documentation :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103-T09-2/main == Introduction diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc index 436c1777617..5acab2c3343 100644 --- a/docs/LearningOutcomes.adoc +++ b/docs/LearningOutcomes.adoc @@ -33,7 +33,7 @@ What other user stories do you think AddressBook should support? Add those user === Exercise: Add a 'Rename tag' use case * Add a use case to the `DeveloperGuide.adoc` to cover the case of _renaming of an existing tag_. -e.g. rename the tag `friends` to `buddies` (i.e. all persons who had the `friends` tag will now have +e.g. rename the tag `friends` to `buddies` (i.e. all customers who had the `friends` tag will now have a `buddies` tag instead) Assume that AddressBook confirms the change with the user before carrying out the operation. @@ -108,7 +108,7 @@ image::PrintableInterface.png[width=400] String getPrintableString(Printable... printables) { ---- + -The above method can be used to get a printable string representing a bunch of person details. +The above method can be used to get a printable string representing a bunch of customer details. For example, you should be able to call that method like this: + [source,java] diff --git a/docs/SettingUp.adoc b/docs/SettingUp.adoc index c0659782fab..a97b770fcc7 100644 --- a/docs/SettingUp.adoc +++ b/docs/SettingUp.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Setting Up += iTrack Pro - Setting Up :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103-T09-2/main == Prerequisites diff --git a/docs/Testing.adoc b/docs/Testing.adoc index 5767b92912c..ae3adb59f5a 100644 --- a/docs/Testing.adoc +++ b/docs/Testing.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Testing += iTrack Pro - Testing :site-section: DeveloperGuide :toc: :toc-title: diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 4e5d297a19f..ca024a884b0 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - User Guide += iTrack Pro - User Guide :site-section: UserGuide :toc: :toc-title: @@ -12,34 +12,47 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3 +:repoURL: https://github.com/AY1920S2-CS2103-T09-2/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `AY1920S2-CS2103-T09-2` Since: `Feb 2020` Licence: `MIT` == Introduction -AddressBook Level 3 (AB3) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB3 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +iTrack Pro is for the grocery shop owners to keep track of the products, customers, and transactions of the shop and have a better understanding of the business by viewing the performance of products and behaviors of customers. It is also able to provide an analysis of the entire business to help the owner manage the shop. == Quick Start . Ensure you have Java `11` or above installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. +. Download the latest `iTrackPro.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your Inventory System. . Double-click the file to start the app. The GUI should appear in a few seconds. + -image::Ui.png[width="790"] +image::Ui.png[width="790" align="center"] + . Type the command in the command box and press kbd:[Enter] to execute it. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. . Some example commands you can try: -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list +* *`listc`* : lists all customers +* **`addc`** `n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a customer to the application +* **`deletec`**`3` : deletes the 3rd customer shown in the current list * *`exit`* : exits the app . Refer to <> for details of each command. +[[Legend]] +== Legend +Please take note of the three different types of notification that are used in the features section: + +[NOTE] +This tells you some things to take note of. It also helps to explain why we did what we did. + +[TIP] +This provides tips on using the command the best way, and alternatives that can be used. + +[WARNING] +Pay attention to the warnings before you execute the command, or else things may not go according to what you expect. + [[Features]] == Features @@ -52,126 +65,664 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. * Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. ==== -=== Viewing help : `help` +=== General + +// tag::help[] +==== Viewing help : `help` Format: `help` +View the user guide page in a pop-out window. +// end::help[] + +image::Help.png[align="center"] + +// tag::receivenotif[] +==== Receiving notification while a certain product is running low. + +Pops up notification if quantity of product is below threshold. + +Displays the product name and remaining product quantity. + +image::PopupNotification.png[align="center"] + +[NOTE] +This is a passive feature. +// end::receivenotif[] + + +// tag::exit[] +==== Exiting from the program : `exit` + +Exits from the program, closes all opened windows. + +Format: `exit` +// end::exit[] + +// tag::savedata[] +==== Saving the data + +The application data is saved in the hard disk automatically after any command that changes the data. + + +[NOTE] +There is no need to save manually. +// end::savedata[] + +// tag::reuseinputs[] +==== Reusing previous inputs + +The application keeps the history of previous inputs that was keyed in the command line. + +**** +* Can keep history of up to 100 inputs. + +* Up arrow key in command line to navigate up the history of inputs. + +* Down arrow key in command line to navigate down the history of inputs. + +* Commands are saved into the history automatically. +**** -=== Adding a person: `add` +[WARNING] +The history is deleted after application closes. +// end::reuseinputs[] -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +// tag::lowstockproducts[] +==== Keeping track of products that are running low on stock + +The product list updates and sorts by the progress bar indicator when user uses the `listp` command. + +Products are ordered by the level of progress bar indicator. (E.g. the lower the bar, the higher it is in the list). +This is to help the user easier to know which products are running low on stock. + +image::ProductCard.png[align="center" width="600"] + +**** +* The progress bar indicator beside the products' name visualises the remaining balance. + +* The bar color depends on quantity / threshold * 5, and changes as the level decreases, from green -> yellow -> orange -> red. + +**** + +[NOTE] +This is a passive feature. + +Green: stock > 60% + +Yellow: 40% < stock < 60% + +Orange: 20% < stock < 40% + +Red: stock < 20% + +// end::lowstockproducts[] + +=== Manage customers + +// tag::addc[] +==== Adding a customer: `addc` + +Adds a customer to the customer list + +Format: `addc n/NAME p/PHONE_NUMBER [e/EMAIL] [a/ADDRESS] [t/TAG]…` + +**** +* A customer can have up to 5 tags (including 0). + +* Duplicate customers (with the same name, phone, email, address) are not allowed. + +* The address field can take up to 45 characters. + +* The name field can take up to 30 characters. + +* The phone field can be between 3 to 15 characters long and contain only integers. + +* The email field can take up to 40 characters and should be a valid email format. + +* Duplicate fields, except tags, are not allowed. E.g. keying in the email field for more +than one time. +**** [TIP] -A person can have any number of tags (including 0) +The email field [e/] is optional, and will be recorded as N/A if left empty. + +The address field [a/] is optional, and will be recorded as N/A if left empty. Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `addc n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` +* `addc n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +// end::addc[] -=== Listing all persons : `list` +// tag::listc[] +==== Listing all customers: `listc` -Shows a list of all persons in the address book. + -Format: `list` +Shows a list of all customers in the customer list. + +Format: `listc` +// end::listc[] -=== Editing a person : `edit` +// tag::editc[] +==== Editing a customer: `editc` -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Edits an existing customer in the customer list. + +Format: `editc INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` **** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index *must be a positive integer* 1, 2, 3, ... +* Edits the customer at the specified `INDEX`. The index refers to the index number shown in the displayed customer list. The index must be a positive integer 1, 2, 3, …​ * At least one of the optional fields must be provided. * Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. +* When editing tags, the existing tags of the customer will be removed i.e adding of tags is not cumulative. +* You can remove all the customer’s tags by typing t/ without specifying any tags after it. +* Duplicate fields, except tags, are not allowed. E.g. keying in the email field for more +than one time. **** Examples: -* `edit 1 p/91234567 e/johndoe@example.com` + -Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` + -Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +* `editc 1 p/91234567 e/johndoe@example.com` + +Edits the phone number and email address of the 1st customer to be 91234567 and johndoe@example.com respectively. +* `editc 2 n/Betsy Crower t/` + +Edits the name of the 2nd customer to be Betsy Crower and clears all existing tags. +// end::editc[] -=== Locating persons by name: `find` +// tag::findc[] +==== Finding customers: `findc` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Finds customers whose attributes match the given attributes. + +Format: `findc [n/NAME [NAME]...] [p/PHONE] [e/EMAIL] [a/ADDRESS [ADDRESS]...]` **** -* The search is case insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +* The search is case insensitive. e.g hans will match Hans +* At least one of the optional fields must be provided. +* Only full words will be matched for keywords e.g. Han will not match Hans +* The order of the keywords and attributes does not matter. e.g. Hans Bo will match Bo Hans, n/Bob a/31 will match a/31 n/Bob +* Only customers matching all attributes will be returned (i.e. AND search) e.g. `findc n/Jane a/31` will return customers with 'jane' in their names and '31' in their addresses +* Customers matching any keywords in an attribute (for name and address only) will be returned (i.e. OR search) e.g. `findc a/clementi ave` will return customers with 'clementi' or 'ave' in their addresses +* Duplicate fields, except tags, are not allowed. E.g. keying in the phone field for more +than one time. **** +image::FindCustomerByAddress.png[width="790" align="center"] +image::FindCustomerByAddress2.png[width="790" align="center"] + +[NOTE] +Only name and address support multiple keywords + Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `findc n/John` + +Returns all customers with names John from the customer list. +* `findc n/Betsy Tim John` + +Returns any customer having names Betsy, Tim, *OR* John in the customer list. +* `findc a/serangoon n/Bob` + +Returns all customers with addresses in Serangoon *AND* Bob in their names. +// end::findc[] -// tag::delete[] -=== Deleting a person : `delete` +// tag::deletec[] +==== Deleting a customer: `deletec` -Deletes the specified person from the address book. + -Format: `delete INDEX` +Deletes the specified customer from the customer list that is currently being displayed. + +Format: `deletec INDEX` **** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. +* Deletes the customer at the specified `INDEX`. +* The index refers to the index number shown in the displayed customer list. +* The index must be a positive integer 1, 2, 3, …​ +**** + +[WARNING] +Deletes transactions that are associated with this particular customer as well. + +Examples: + +* `listc` + +`deletec 2` + +Deletes the 2nd customer in the customer list. +* `findc n/Betsy` + +`deletec 1` + +Deletes the 1st customer in the results of the find command. +// end::deletec[] + +// tag::clearc[] +==== Clearing all customers: `clearc` + +Clears all entries from the customer list. + +Format: `clearc` + +[WARNING] +Permanently deletes all the stored customer data in the application. + +Deletes all transactions as well. +// end::clearc[] + +=== Manage product + +// tag::addp[] +==== Adding a product: `addp` + +Adds a product to the product list. + +Format: `addp d/DESCRIPTION pr/PRICE q/QUANTITY cp/COSTPRICE [s/SALES]` + +**** +* Duplicate products (with the same description, cost price, price) are not allowed. + +* The price, cost price, quantity and sales can take integers up to 1000000. + +* The price and cost price must be at least $1. +* Duplicate fields are not allowed. E.g. keying in the price field for more +than one time. +**** + +[NOTE] +The default threshold value is 20% of quantity of product. + +[TIP] +A product created without providing values for sales (in SGD) will be created with 0 sales. + +image::AddProduct.png[width="790" align="center"] + +Examples: + +* `addp d/iphone x pr/1000 q/10 cp/300` +* `addp d/camera pr/2000 q/90 s/100 cp/1000` +// end::addp[] + +// tag::listp[] +==== Listing all products : `listp` + +Show all products in the product list. + +Format: `listp` + +[NOTE] +Sorts all of the product by the product quantity, represented by the bar indicator beside the product name. +// end::listp[] + +// tag::editp[] +==== Editing a product : `editp` + +Edits an existing product in the displayed product list. + +Format: `editp INDEX [d/DESCRIPTION] [pr/PRICE] [q/QUANTITY] [cp/COSTPRICE] [s/SALES]` + +**** +* Edits the product at the specified `INDEX`. The index refers to the index number shown in the displayed product list. The index must be a positive integer 1, 2, 3, …​ +* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +* Duplicate fields are not allowed. E.g. keying in the price field for more +than one time. +**** + +Examples: + +* `editp 1 pr/1150 q/80` + +Edits the price and quantity of the 1st product in the list to be $1150 and 80 respectively. +* `editp 2 s/1000` + +Edits the sales of the 2nd product in the list to be $1000. +// end::editp[] + +// tag::findp[] +==== Finding products : `findp` + +Finds products whose description contains a certain keyword + +Format: `findp KEYWORD [KEYWORD]...` + +**** +* The search is case insensitive. e.g blue will match Blue +* At least one of the option fields must be provided. +* Only full words will be matched for keywords e.g. blu will not match blue +* The keyword will be searched only in the product’s description. +* The order of the keywords does not matter. e.g. blue shoes will match shoes blue +* Products matching at least one keyword will be returned (i.e. OR search). e.g. blue shoes will return blue slippers, red shoes +**** + +image::FindProduct.png[width="790" align="center"] + +Examples: + +* `findp camera` + +Returns all product with description `camera` in it. +* `findp iphone` + +Returns all product with description `iPhone` in it. +// end::findp[] + +// tag::deletep[] +==== Deleting a product : `deletep` + +Deletes the specified product from the system. + +Format: `deletep INDEX` + +**** +* Deletes the product at the specified `INDEX`. +* The index refers to the index number shown in the displayed product list. * The index *must be a positive integer* 1, 2, 3, ... **** +[WARNING] +This action deletes transactions that are associated with this particular product as well. + Examples: -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +* `listp` + +`deletep 2` + +Deletes the 2nd product in the product list. +* `findp camera` + +`deletep 1` + +Deletes the 1st product in the results of the find command. +// end::deletep[] -// end::delete[] -=== Clearing all entries : `clear` +// tag::clearp[] +==== Clearing all products : `clearp` -Clears all entries from the address book. + -Format: `clear` +Clears all entries from the product list. + +Format: `clearp` -=== Exiting the program : `exit` +[WARNING] +Permanently deletes all the stored product data in the application. + +Deletes all transactions as well. +// end::clearp[] -Exits the program. + -Format: `exit` +=== Transaction -=== Saving the data +User can navigate to the transaction panel by clicking on the transactions tab. -Address book data are saved in the hard disk automatically after any command that changes the data. + -There is no need to save manually. +image::TransactionTab.png[width="200" align="center"] + +// tag::addt[] +==== Adding a transaction : `addt` -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +Adds a specified transaction to the system. + +Format: `addt p/PRODUCT_ID c/CUSTOMER_ID q/QUANTITY [dt/DATETIME] [m/MONEY] [d/DESCRIPTION]` + -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +**** +* Duplicate transactions (with the same customer, product, datetime) are not allowed. + +* The quantity and money fields can take integers up to 1000000. + +* The quantity must be at least 1. +* Duplicate fields are not allowed. E.g. keying in the quantity field for more +than one time. +**** + +[TIP] +The date time field [dt/] is optional, and will be recorded as current local machine time if left empty. + +The money field [m/] is optional, and will be recorded as product price multiplied by quantity if left empty. +Only need to enter if necessary (i.e. discounts on products). + +The description field [d/] is optional, will be recorded as N/A if left empty. +Only need to enter if user wants to add notes to the transaction. + +[WARNING] +Adding transaction will update the quantity and sales of its associated product as well. + +image::AddTransaction.png[width="790" align="center"] +After adding transactions, the transaction list can be viewed under the transaction tab. + +image::AddTransaction2.png[width="790" align="center"] + +Examples: + +* `addt p/1 c/10 dt/2020-02-19 19:00 q/10 m/20` + +Adds a transaction, where the 10th customer bought 10 of the 1st product for $20 at 2020-02-19 19:00. +* `addt p/20 c/2 dt/2020-02-20 10:00 q/10 m/30 d/under discount` + +Adds a transaction, where the 2nd customer bought 10 of the 20th product for $30 at 2020-02-20 10:00 at an discount. +// end::addt[] + +// tag::listt[] +==== Listing all transactions : `listt` + +Lists all the transactions. + +Format: `listt` +// end::listt[] + +// tag::editt[] +==== Editing a transaction : `editt` + +Edits a transaction in the system. It allows the user to edit wrong transction + with correct information. + +Format: `editt INDEX [p/PRODUCT_ID] [c/CUSTOMER_ID] [dt/DATE_TIME] [q/QUANTITY] [m/MONEY] [d/DESCRIPTION]` + +**** +* Edits the transaction at the specified `INDEX`. The index refers to the index number shown in the displayed transaction list. The index must be a positive integer 1, 2, 3, …​ +* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +* Duplicate fields are not allowed. E.g. keying in the quantity field for more +than one time. +**** + +[WARNING] +Editting the quantity or money of a transaction will update the quantity and sales +of its associated product as well. + +Examples: + +* `editt 1 p/101 c/123` + +Edits the product id and customer id of the 1st transaction to be 101 and 123 respectively. +// end::editt[] + +// tag::findt[] +==== Finding transactions : `findt` + +Finds transactions whose attributes match the given attributes. + +Format: `findt [p/PRODUCT_NAME [PRODUCT_NAME]...] [c/CUSTOMER_NAME [CUSTOMER_NAME]...] [dt/DATE_TIME] [m/MONEY]` + +**** +* The search is case insensitive. e.g blue will match Blue +* At least one of the option fields must be provided. +* The order of the keywords and attributes does not matter. e.g. Hans Bo will match Bo Hans, c/Bob m/31 will match m/31 c/Bob. +* Only transactions matching all attributes will be returned (i.e. AND search). e.g. `findt c/Jane p/bag` will return transactions with 'jane' in their customer names and 'bag' in their product names. +* Transactions matching any keywords in an attribute (for product or customer name only) will be returned (i.e. OR search) e.g. `findt c/jane avery` will return transactions with 'jane' or 'avery' in their customer names. +* Duplicate fields are not allowed. E.g. keying in the `DATE_TIME` field for more +than one time. +**** + +image::FindTransactionByProduct.png[width="790" align="center"] +image::FindTransactionByCustomer.png[width="790" align="center"] + +[NOTE] +Only product name and customer name support multiple keywords + +Examples: + +* `findt p/iphone` + +Returns all transactions that involve the product `iphone`. +* `findt c/bob angie` + +Returns all transactions that involve a customer named Bob *OR* a customer named Angie. +* `findt dt/2020-02-07 16:00` + +Returns all transactions made on 7th February 2020 4pm. +* `findt m/100` + +Returns all transactions that have an amount of 100 dollars. +* `findt c/bob dt/2020-02-07 16:00` + +Returns all transactions that Bob made on the 7th February 2020 4pm. +// end::findt[] + +// tag::undot[] +==== Undo a transaction : `undot` + +Undo the specified transaction from the system. It allows the user to remove a transaction in case he/she keyed inaccurate +information. + +Format: `undot INDEX` + +**** +* Undos the transaction at the specified `INDEX`. +* The index refers to the index number shown in the displayed transaction list. +* The index must be a positive integer 1, 2, 3, …​ +**** + +[NOTE] +Why undot instead of deletet? Deletet implies that transaction is only deleted but undot is more fitting as the product details will be modified too. + +[WARNING] +Adds the quantity in the transaction back to the product and reduces the sales of the product by transaction amount. + +Examples: + +* `listt` + +`undot 2` + +Undo the 2nd transaction in the displayed list. +* `findt dt/2020-01-03 16:00` + +`undot 1` + +Undo the 1st transaction in the results of the find command. +// end::undot[] + +// tag::cleart[] +==== Clearing all transactions : `cleart` + +Clears all transactions from the list of transactions. + +Format: `cleart` + +[WARNING] +Permanently deletes all the stored transaction data in the application. +// end::cleart[] + +=== Statistics + +User can navigate to the statistics panel by clicking on the statistics tab. + +image::StatisticsTab.png[width="200" align="center"] + +// tag::viewtopsellingproducts[] +==== Viewing the top-selling and worst-selling products. + +Displays and updates the top-selling and worst-selling products (sorted by profit) as transactions are made. + + +[NOTE] +This is a passive feature. +// end::viewtopsellingproducts[] + +image::TopSellingProducts.png[width="650"] + +// tag::viewproductsalesquantity[] +==== Viewing the product sales and quantity histogram. + +Displays and updates the histogram of product sales and quantity. + +Plots 2 graphs: + +1. Number of products against quantity of products + +2. Number of products against sales of products + +[NOTE] +This is a passive feature. + +image::ViewInventory2.png[width="600"] +// end::viewproductsalesquantity[] + +// tag::revenue[] +==== Getting the revenue made in a certain period : `revenue` + +Gets the revenue made in a selected period. (start date to end date, both inclusive) + +Format: `revenue [sd/START_DATE] [ed/END_DATE]` + +**** +* The start date and end date must follow a format of `yyyy-mm-dd hh:mm` +* The start date must be before or equal to end date +* At least one product must be present +**** + +Example: + +* `revenue sd/2020-01-01 10:00 ed/2020-12-31 10:01` + +Returns the revenue from Jan 1 2020 10am to Dec 31 2020 10:01am +// end::revenue[] + +// tag::profit[] +==== Getting the profit made in a certain period : `profit` + +Gets the profit made in a selected period (start date to end date, both inclusive). + +Format: `profit [sd/START_DATE] [ed/END_DATE]` + +**** +* The start date and end date must follow a format of `yyyy-mm-dd hh:mm` +* The start date must be before or equal to end date +* At least one product must be present +**** + +Example: + +* `profit sd/2020-01-01 10:00 ed/2020-12-31 10:01` + +Returns the profit from Jan 1 2020 10am to Dec 31 2020 10:01am +// end::profit[] + +// tag::lowlimit[] +==== Setting the low-inventory threshold : `lowlimit` + +Sets the notification threshold for individual product and updates the bar indicator of the product. + +Format: `lowlimit p/PRODUCT_ID t/THRESHOLD` + +**** +* `THRESHOLD` must be non-negative integers, i.e. 1, 2, 3, ... +* `PRODUCT_ID` refers to the index number shown in the displayed products list. +* The index must be a positive integer 1, 2, 3, …​ +**** + +[NOTE] +The default threshold represents 20% of the desired quantity. + +The quantity threshold can take integers up to 1000000. + +Examples: + +* `lowlimit p/1 t/20` + +Sets the low inventory threshold for the 1st product as 20. +// end::lowlimit[] + +// tag::predict[] +==== Predicting the sales for the next month : `predict` + +Predicts sales for the next month based on sales in the previous three months + +Format: `predict` + +[NOTE] +The average of the profits made in the past three months is the predicted sales for next month. +// end::predict[] + +// tag::plotsales[] +==== Plotting sales of a product: `plotsales` + +Plots a graph with the sales of the selected product in a given time period. + +Format: `plotsales PRODUCT_INDEX [sd/START_DATE] [ed/END_DATE]` + +**** +* The start date and end date must follow a format of `yyyy-mm-dd hh:mm` +* The start date must be before or equal to end date +* At least one product must be present +**** + +[TIP] +The start date and end date attributes are optional. If omitted, the system +will plot the last 7 days by default. + +Examples: + +* `plotsales 1 sd/2020-02-20 10:00 ed/2020-02-28 10:01` + +Plots a graph with the sales of the selected product between 20th Feb 10am and 28th Feb 10:01am in 2020. + +* `plotsales 1` + +Plots a graph with the sales of the selected product in the past week. + +image::SalesPlot.png[width="500"] +// end::plotsales[] == FAQ +*Q*: How to delete a product? + +*A*: First, display a list of product, e.g. `listp`. Then type `deletep INDEX` where the index refers to the index displayed in the list. Refer to <>. + *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Inventory System folder. == Command Summary -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` +* *Adding customer* : `addc n/NAME p/PHONE_NUMBER [e/EMAIL] [a/ADDRESS] [t/TAG]…` + +e.g. `addc n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` +* *Listing all customers* : `listc` +* *Editing customer information* : `editc INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` + +e.g. `editc 1 p/91234567 e/johndoe@example.com` +* *Finding customers* : `findc [n/NAME [NAME]...] [p/PHONE] [e/EMAIL] [a/ADDRESS [ADDRESS]...]` + +e.g. `findc n/John` +* *Deleting a customer* : `deletec INDEX` + +e.g. `findc n/Betsy` + +`deletec 1` +* *Clearing all customers* : `clearc` +* *Adding a product* : `addp d/DESCRIPTION pr/PRICE q/QUANTITY cp/COSTPRICE [s/SALES]` + +e.g. `addp d/iphone x pr/1000 cp/800 q/10` +* *Listing all products* : `listp` +* *Editing a product* : `editp INDEX [d/DESCRIPTION] [pr/PRICE] [cp/COSTPRICE] [q/QUANTITY] [s/SALES]` + +e.g. `editp 1 pr/1150 q/80` +* *Finding products* : `findp KEYWORD [KEYWORD]...` + +e.g. `findp black` +* *Deleting a product* : `deletep INDEX` + +e.g. `listp` + +`deletep 2` +* *Clearing all products* : `clearp` +* *Adding a transaction* : `addt p/PRODUCT_ID c/CUSTOMER_ID dt/DATE_TIME m/MONEY q/QUANTITY [d/DESCRIPTION]` + +e.g. `addt p/20 c/2 dt/2020-02-20 10:00 m/30 q/10 d/under discount` +* *Listing all transactions* : `listt` +* *Editing a transaction* : `editt INDEX [p/PRODUCT_ID] [c/CUSTOMER_ID] [dt/DATE_TIME] [q/QUANTITY] [m/MONEY] [d/DESCRIPTION]` + +e.g. `editt 1 p/101 c/123` +* *Finding transactions* : `findt [p/PRODUCT_NAME [PRODUCT_NAME]...] [c/CUSTOMER_NAME [CUSTOMER_NAME]...] [dt/DATE_TIME] [m/MONEY]` + +e.g. `findt c/bob dt/2020-02-07 10:00` +* *Undo a transaction* : `undot INDEX` + +e.g. `findt dt/2020-01-03 10:00` + +`undot 1` +* *Clearing all transactions* : `cleart` +* *Get the revenue made in a certain period* : `revenue sd/START_DATE ed/END_DATE` + +e.g. `revenue sd/2020-01-01 10:00 ed/2020-12-12 10:01` +* *Get the profit made in a certain period* : `profit sd/START_DATE ed/END_DATE` + +e.g. `profit sd/2020-01-01 10:00 ed/2020-12-12 10:01` +* *Setting the low-inventory threshold* : `lowlimit p/PRODUCT_ID t/THRESHOLD` + +e.g. `lowlimit p/1 t/20` +* *Predicting the sales for the next month* : `predict` +* *Plotting sales* : `plotsales PRODUCT_INDEX [sd/START_DATE] [ed/END_DATE]` + +e.g. `plotsales 1 sd/2020-03-20 10:00 ed/2020-03-30 10:00` +* *Exiting from the program* : `exit` +* *Get help* : `help` diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml index fdcbe1c0ccc..151eafe7adb 100644 --- a/docs/diagrams/tracing/LogicSequenceDiagram.puml +++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml @@ -13,7 +13,7 @@ create ecp abp -> ecp abp -> ecp ++: parse(arguments) create ec -ecp -> ec ++: index, editPersonDescriptor +ecp -> ec ++: index, editCustomerDescriptor ec --> ecp -- ecp --> abp --: command abp --> logic --: command diff --git a/docs/images/AddCustomerSequenceDiagram.png b/docs/images/AddCustomerSequenceDiagram.png new file mode 100644 index 00000000000..d211e93d2f2 Binary files /dev/null and b/docs/images/AddCustomerSequenceDiagram.png differ diff --git a/docs/images/AddItemSequenceDiagram.png b/docs/images/AddItemSequenceDiagram.png new file mode 100644 index 00000000000..0d8ed8648d8 Binary files /dev/null and b/docs/images/AddItemSequenceDiagram.png differ diff --git a/docs/images/AddProduct.png b/docs/images/AddProduct.png new file mode 100644 index 00000000000..1403a79bfd5 Binary files /dev/null and b/docs/images/AddProduct.png differ diff --git a/docs/images/AddProductSequenceDiagram.png b/docs/images/AddProductSequenceDiagram.png new file mode 100644 index 00000000000..919a5ff136b Binary files /dev/null and b/docs/images/AddProductSequenceDiagram.png differ diff --git a/docs/images/AddTransaction.png b/docs/images/AddTransaction.png new file mode 100644 index 00000000000..fd1c8e2906d Binary files /dev/null and b/docs/images/AddTransaction.png differ diff --git a/docs/images/AddTransaction2.png b/docs/images/AddTransaction2.png new file mode 100644 index 00000000000..8255fc4d4f3 Binary files /dev/null and b/docs/images/AddTransaction2.png differ diff --git a/docs/images/AddTransactionSequenceDiagram.png b/docs/images/AddTransactionSequenceDiagram.png new file mode 100644 index 00000000000..86c4e1ba17b Binary files /dev/null and b/docs/images/AddTransactionSequenceDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index aa198138f8f..696208fe631 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index bc7ed18ae29..1b1c5feea1f 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/CommandActivityDiagram.png b/docs/images/CommandActivityDiagram.png new file mode 100644 index 00000000000..a77db13154e Binary files /dev/null and b/docs/images/CommandActivityDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..8f3a503330f 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/EditCustomerSequenceDiagram.png b/docs/images/EditCustomerSequenceDiagram.png new file mode 100644 index 00000000000..7be94d20b5f Binary files /dev/null and b/docs/images/EditCustomerSequenceDiagram.png differ diff --git a/docs/images/EditItemSequenceDiagram.png b/docs/images/EditItemSequenceDiagram.png new file mode 100644 index 00000000000..87fb102c9fc Binary files /dev/null and b/docs/images/EditItemSequenceDiagram.png differ diff --git a/docs/images/EditProductSequenceDiagram.png b/docs/images/EditProductSequenceDiagram.png new file mode 100644 index 00000000000..e8a4d557275 Binary files /dev/null and b/docs/images/EditProductSequenceDiagram.png differ diff --git a/docs/images/EditTransactionSequenceDiagram.png b/docs/images/EditTransactionSequenceDiagram.png new file mode 100644 index 00000000000..50bcfe3cd0d Binary files /dev/null and b/docs/images/EditTransactionSequenceDiagram.png differ diff --git a/docs/images/FindCustomerByAddress.png b/docs/images/FindCustomerByAddress.png new file mode 100644 index 00000000000..5bf888fa9f1 Binary files /dev/null and b/docs/images/FindCustomerByAddress.png differ diff --git a/docs/images/FindCustomerByAddress2.png b/docs/images/FindCustomerByAddress2.png new file mode 100644 index 00000000000..1d71c62c5ad Binary files /dev/null and b/docs/images/FindCustomerByAddress2.png differ diff --git a/docs/images/FindProduct.png b/docs/images/FindProduct.png new file mode 100644 index 00000000000..61eaf9f754e Binary files /dev/null and b/docs/images/FindProduct.png differ diff --git a/docs/images/FindTransaction.png b/docs/images/FindTransaction.png new file mode 100644 index 00000000000..b2d183bf3bc Binary files /dev/null and b/docs/images/FindTransaction.png differ diff --git a/docs/images/FindTransactionByCustomer.png b/docs/images/FindTransactionByCustomer.png new file mode 100644 index 00000000000..35c2fef2cd3 Binary files /dev/null and b/docs/images/FindTransactionByCustomer.png differ diff --git a/docs/images/FindTransactionByProduct.png b/docs/images/FindTransactionByProduct.png new file mode 100644 index 00000000000..e013e9cf00e Binary files /dev/null and b/docs/images/FindTransactionByProduct.png differ diff --git a/docs/images/ListAllCustomers.png b/docs/images/ListAllCustomers.png new file mode 100644 index 00000000000..2addc2d8165 Binary files /dev/null and b/docs/images/ListAllCustomers.png differ diff --git a/docs/images/ListCustomerSequenceDiagram.png b/docs/images/ListCustomerSequenceDiagram.png new file mode 100644 index 00000000000..7456ea4f95f Binary files /dev/null and b/docs/images/ListCustomerSequenceDiagram.png differ diff --git a/docs/images/ListItemSequenceDiagram.png b/docs/images/ListItemSequenceDiagram.png new file mode 100644 index 00000000000..65a0d036a62 Binary files /dev/null and b/docs/images/ListItemSequenceDiagram.png differ diff --git a/docs/images/ListProductSequenceDiagram.png b/docs/images/ListProductSequenceDiagram.png new file mode 100644 index 00000000000..fa9d2d99f1d Binary files /dev/null and b/docs/images/ListProductSequenceDiagram.png differ diff --git a/docs/images/ListTransactions.png b/docs/images/ListTransactions.png new file mode 100644 index 00000000000..c74d9266230 Binary files /dev/null and b/docs/images/ListTransactions.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index b9e853cef12..762fe669ef4 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/LowLimitCommandSequenceDiagram.png b/docs/images/LowLimitCommandSequenceDiagram.png new file mode 100644 index 00000000000..86444af1053 Binary files /dev/null and b/docs/images/LowLimitCommandSequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 280064118cf..22ef3bb08fb 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/NotificationWindowActivityDiagram.png b/docs/images/NotificationWindowActivityDiagram.png new file mode 100644 index 00000000000..09665917c19 Binary files /dev/null and b/docs/images/NotificationWindowActivityDiagram.png differ diff --git a/docs/images/PlotSalesSequenceDiagram.png b/docs/images/PlotSalesSequenceDiagram.png new file mode 100644 index 00000000000..3a921064a9e Binary files /dev/null and b/docs/images/PlotSalesSequenceDiagram.png differ diff --git a/docs/images/PopupNotification.png b/docs/images/PopupNotification.png new file mode 100644 index 00000000000..5638659645d Binary files /dev/null and b/docs/images/PopupNotification.png differ diff --git a/docs/images/ProductCard.png b/docs/images/ProductCard.png new file mode 100644 index 00000000000..a7f07e23170 Binary files /dev/null and b/docs/images/ProductCard.png differ diff --git a/docs/images/ProfitSequenceDiagram.png b/docs/images/ProfitSequenceDiagram.png new file mode 100644 index 00000000000..a2f241b8e79 Binary files /dev/null and b/docs/images/ProfitSequenceDiagram.png differ diff --git a/docs/images/SalesPlot.png b/docs/images/SalesPlot.png new file mode 100644 index 00000000000..ee0edf3ac2f Binary files /dev/null and b/docs/images/SalesPlot.png differ diff --git a/docs/images/SortProductListActivityDiagram.png b/docs/images/SortProductListActivityDiagram.png new file mode 100644 index 00000000000..a75dc0e50ad Binary files /dev/null and b/docs/images/SortProductListActivityDiagram.png differ diff --git a/docs/images/SortingExample.png b/docs/images/SortingExample.png new file mode 100644 index 00000000000..17a05f182f7 Binary files /dev/null and b/docs/images/SortingExample.png differ diff --git a/docs/images/StatisticsTab.png b/docs/images/StatisticsTab.png new file mode 100644 index 00000000000..a7cc58be1ab Binary files /dev/null and b/docs/images/StatisticsTab.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index d87c1216820..716481d9796 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/TopSellingProduct 2.png b/docs/images/TopSellingProduct 2.png new file mode 100644 index 00000000000..1a3a2b57b30 Binary files /dev/null and b/docs/images/TopSellingProduct 2.png differ diff --git a/docs/images/TopSellingProduct.png b/docs/images/TopSellingProduct.png new file mode 100644 index 00000000000..a442bfd44b7 Binary files /dev/null and b/docs/images/TopSellingProduct.png differ diff --git a/docs/images/TopSellingProducts.png b/docs/images/TopSellingProducts.png new file mode 100644 index 00000000000..1007d7655e5 Binary files /dev/null and b/docs/images/TopSellingProducts.png differ diff --git a/docs/images/TrackProductBalance.png b/docs/images/TrackProductBalance.png new file mode 100644 index 00000000000..fb2a6e863cb Binary files /dev/null and b/docs/images/TrackProductBalance.png differ diff --git a/docs/images/TransactionTab.png b/docs/images/TransactionTab.png new file mode 100644 index 00000000000..4f6b7924b20 Binary files /dev/null and b/docs/images/TransactionTab.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..9010b10dc15 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 7b4b3dbea45..24e4756a1da 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoTransactionSequenceDiagram.png b/docs/images/UndoTransactionSequenceDiagram.png new file mode 100644 index 00000000000..a739425e287 Binary files /dev/null and b/docs/images/UndoTransactionSequenceDiagram.png differ diff --git a/docs/images/ViewInventory.png b/docs/images/ViewInventory.png new file mode 100644 index 00000000000..6055e66df4f Binary files /dev/null and b/docs/images/ViewInventory.png differ diff --git a/docs/images/ViewInventory2.png b/docs/images/ViewInventory2.png new file mode 100644 index 00000000000..647e08d04f4 Binary files /dev/null and b/docs/images/ViewInventory2.png differ diff --git a/docs/images/YingxuH.png b/docs/images/YingxuH.png new file mode 100644 index 00000000000..ea56a9f7e7f Binary files /dev/null and b/docs/images/YingxuH.png differ diff --git a/docs/images/aliciatxl.png b/docs/images/aliciatxl.png new file mode 100644 index 00000000000..b51947573f4 Binary files /dev/null and b/docs/images/aliciatxl.png differ diff --git a/docs/images/help.png b/docs/images/help.png new file mode 100644 index 00000000000..477c8db1b97 Binary files /dev/null and b/docs/images/help.png differ diff --git a/docs/images/junhaotan.png b/docs/images/junhaotan.png new file mode 100644 index 00000000000..ea56a9f7e7f Binary files /dev/null and b/docs/images/junhaotan.png differ diff --git a/docs/team/aliciatxl.adoc b/docs/team/aliciatxl.adoc new file mode 100644 index 00000000000..42b8fa8d31b --- /dev/null +++ b/docs/team/aliciatxl.adoc @@ -0,0 +1,95 @@ += Tan Su Yee - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: iTrack Pro + +--- + +== Overview + +iTrack Pro is for the grocery shop owners to keep track of the products, customers, and transactions of the shop and have a better understanding of the business by viewing the performance of products and behaviors of customers. It is also able to provide an analysis of the entire business to help the owner manage the shop. + +== Summary of contributions + +* *Major enhancement*: +1. Added *the ability to manage products (add, list, find, delete, clear, edit)* +** What it does: allows the user to manage products in the inventory easily. User can add, delete or edit products, and list or clear all products. Users can also find products based on keywords. +** Justification: This feature improves the product significantly because shop owners can have many products and managing them manually can be inefficient. Hence, the inventory system provides a convenient way for them to keep track of their products. +** Highlights: Though the implementation seems similar to the `Person` class in the addressbook, it was challenging as the fields were different and the execution of some commands varied due to the different requirements. + +2. Added *the ability to view and analyse statistics (revenue, profit, predict sales, view top selling products)* +** What it does: allows the user to view and analyse statistics based on transactions added to the inventory system. +** Justification: This feature improves the product significantly because shop owners can gain insight to their business and know how profitable they are. By knowing the revenue and profit, the user can gauge market demand for their products and employ suitable marketing strategies to improve sales. Moreover, users can get the predicted sales for the next month, enabling them to make more informed business decisions. +** Highlights: This enhancement required an in-depth analysis of design alternatives. The difficult part was to determine how and when the revenue/ profit was to be calculated, and the best way to structure the classes. + +* *Minor enhancement*: +** Added id for `Customer` and `Product` and made use of customer and product id fields to prevent duplicate transactions (during transaction edit) (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/122[#122]) +** Added view top-selling products function (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/112[#112]) +** Updated ui for statistics window and notification (Pull requests https://github.com/AY1920S2-CS2103-T09-2/main/pull/130[#130], https://github.com/AY1920S2-CS2103-T09-2/main/pull/112[#112]) +** Wrote tests for product features to increase coverage by 16.3% to 66.752% (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/82[#82]) +** Wrote tests for statistics features (Pull requests https://github.com/AY1920S2-CS2103-T09-2/main/pull/119[#119], https://github.com/AY1920S2-CS2103-T09-2/main/pull/97[#97]) + +* *Code contributed*: https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=aliciatxl&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false[tp-dashboard] +** [https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/model/product[Product model], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/logic/commands/product[Product commands], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/logic/parser/product[Product parser], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/storage/product[Product storage], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/ui/product[Product ui]] +** [https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/logic/commands/statistics[Statistics commands], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/logic/parser/statistics[Statistics parser], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/ui/statistics[Statistics ui], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/ui[Statistics window]] +** [https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/test/java/seedu/address/logic/commands/product[Test product commands], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/test/java/seedu/address/logic/parser/product[Test product parser], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/test/java/seedu/address/model/product[Test product model], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/test/java/seedu/address/storage[Test product storage], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/test/java/seedu/address/testutil/product[Test product testutil]] +** [https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/test/java/seedu/address/logic/commands/statistics[Test revenue/profit commands]] + +* *Other contributions*: + +** Project management: +*** Reviewed and merge code of teammates +*** Created and assigned issues +** Enhancements to existing features: +*** Updated the GUI color scheme (Pull requests https://github.com/AY1920S2-CS2103-T09-2/main/pull/93[#93], https://github.com/AY1920S2-CS2103-T09-2/main/pull/94[#94], https://github.com/AY1920S2-CS2103-T09-2/main/pull/187[#187]) +*** Added restrictions for each field (e.g. max value for price and quantity, max length for address) (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/142[#142]) +** Documentation: +*** Edited ArchitectureSequenceDiagram, BetterModelClassDiagram, DeleteSequenceDiagram, LogicClassDiagram to reflect current app structure (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/184[#184]) +*** Replaced ModelClassDiagram, UiClassDiagram and StorageClassDiagram with created diagrams to reflect current class structure (Pull requests https://github.com/AY1920S2-CS2103-T09-2/main/pull/184[#184], https://github.com/AY1920S2-CS2103-T09-2/main/pull/187[#187]) +*** Added implementation section, and sequence and activity diagrams for product features (Pull requests https://github.com/AY1920S2-CS2103-T09-2/main/pull/100[#100], https://github.com/AY1920S2-CS2103-T09-2/main/pull/103[#103]) +*** Added implementation section, and sequence and activity diagrams for revenue/profit features (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/184[#184]) +*** Modified glossary and design sections, add target audience, manual testing, effort and use cases to developer guide (Pull requests https://github.com/AY1920S2-CS2103-T09-2/main/pull/19[#19], https://github.com/AY1920S2-CS2103-T09-2/main/pull/194[#194], https://github.com/AY1920S2-CS2103-T09-2/main/pull/202[#202]) +*** Add product features and statistics (revenue, predict, profit) features to user guide (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/24[#24]) +** Community: +*** Reported bugs and suggestions for other teams in the class: https://github.com/AY1920S2-CS2103-T09-1/main[T09-1] +** Tools: +*** Created team organisation on github and set up address book in organisation repo + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=addp] + +include::../UserGuide.adoc[tag=editp] + +include::../UserGuide.adoc[tag=clearp] + +include::../UserGuide.adoc[tag=deletep] + +// include::../UserGuide.adoc[tag=findp] + +include::../UserGuide.adoc[tag=listp] + +include::../UserGuide.adoc[tag=revenue] + +include::../UserGuide.adoc[tag=profit] + +include::../UserGuide.adoc[tag=predict] + +include::../UserGuide.adoc[tag=viewtopsellingproducts] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=manageitem] + +include::../DeveloperGuide.adoc[tag=getprofitrevenue] diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc index f39e76e49b2..a1fc95d9f17 100644 --- a/docs/team/johndoe.adoc +++ b/docs/team/johndoe.adoc @@ -3,13 +3,13 @@ :imagesDir: ../images :stylesDir: ../stylesheets -== PROJECT: AddressBook - Level 3 +== PROJECT: iTrack Pro --- == Overview -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. +iTrack Pro is for the grocery shop owners to keep track of the products, customers, and transactions of the shop and have a better understanding of the business by viewing the performance of products and behaviors of customers. It is also able to provide an analysis of the entire business to help the owner manage the shop. == Summary of contributions @@ -50,9 +50,7 @@ _{you can add/remove categories in the list above}_ |_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ |=== -include::../UserGuide.adoc[tag=delete] - -include::../UserGuide.adoc[tag=dataencryption] +include::../UserGuide.adoc[tag=addp] == Contributions to the Developer Guide diff --git a/docs/team/junhaotan.adoc b/docs/team/junhaotan.adoc new file mode 100644 index 00000000000..84f283f849a --- /dev/null +++ b/docs/team/junhaotan.adoc @@ -0,0 +1,100 @@ += Tan Jun Hao - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: iTrack Pro + +--- + +== Overview + +iTrack Pro is for the grocery shop owners to keep track of the products, customers, and transactions of the shop and have a better understanding of the business by viewing the performance of products and behaviors of customers. It is also able to provide an analysis of the entire business to help the owner manage the shop. + +== Summary of contributions + +* *Major enhancement*: +1. Added *the ability to find customers by specified attributes* +** What it does: allows the user to locate customers easily by name, address, email or phone number. +** Justification: This feature improves the application significantly because user can have many customers, and it can be difficult to keep track of customers manually. +** Highlights: The original find feature was only by name, the implementation has to be changed and refactored to accommodate finding by other attributes too. + +2. Added *visualisation of product balance on Product Ui via progress bar indicator* +** What it does: allows the user to easily identify stocks that are running low. +** Justification: This feature improves the application significantly as users would not have to struggle looking at numbers only to see which stock is running low soon. +** Highlights: This enhancement required an in-depth analysis of design alternatives. The difficult part was to determine how the remaining stock was to be calculated, and the best way to structure the classes. + +* *Minor enhancement*: +** Added a command history feature that allows the user to navigate to previous commands using up/down keys (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/92[#92]) +** Added lowlimit command that allows the user to set individual product low quantity threshold (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/98[#98]) +** Added notification popup window when stock quantity goes below threshold (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/99[#99]) +** Added cost price field to Product class to calculate profit (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/114[#114]) +** Updated ui for statistics window (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/129[#129]) +** Fixed bugs from PE-D (Pull requests https://github.com/AY1920S2-CS2103-T09-2/main/pull/185[#185], https://github.com/AY1920S2-CS2103-T09-2/main/pull/198[#198]) +** Added web view for help window (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/200[#200]) + +* *Code contributed*: https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=junhaotan&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&since=2020-02-14[tP-dashboard] +** [https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/model/customer[Customer model], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/logic/commands/customer[Customer commands], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/logic/parser/customer[Customer parser], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/storage/customer[Customer storage], https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/ui/customer[Customer ui]] +** [https://github.com/AY1920S2-CS2103-T09-2/main/blob/master/src/main/java/seedu/address/logic/commands/product/LowLimitCommand.java[LowLimit command], https://github.com/AY1920S2-CS2103-T09-2/main/blob/master/src/main/java/seedu/address/logic/parser/product/LowLimitCommandParser.java[LowLimit parser], https://github.com/AY1920S2-CS2103-T09-2/main/blob/master/src/main/java/seedu/address/ui/NotificationWindow.java[LowLimit Ui]] +** [https://github.com/AY1920S2-CS2103-T09-2/main/blob/master/src/main/java/seedu/address/ui/CommandBox.java[Command history feature]] +** [https://github.com/AY1920S2-CS2103-T09-2/main/pull/196[InventorySystemParserTest]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.1` - `v1.3` (4 releases) on GitHub +*** Reviewed and merged teammates' pull requests +*** Created and managed milestones & issues +** Enhancements to existing features: +*** Added more conditions for duplicate customers (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/185[#185]) +*** Fix alignment of address label in CustomerCard Ui (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/185[#185]) +*** Added restrictions for number of tags when editing customers (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/185[#185]) +** Documentations: +*** Added user stories in Developer Guide (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/20[#20]) +*** Combined customer and product common features for Developer Guide (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/110[#110]) +*** Updated User Guide to reflect latest application Ui and features (Pull requests https://github.com/AY1920S2-CS2103-T09-2/main/pull/127[#127], https://github.com/AY1920S2-CS2103-T09-2/main/pull/138[#138], https://github.com/AY1920S2-CS2103-T09-2/main/pull/189[#189]) +*** Updated Developer Guide with implemented feature (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/196[#196]) +** Community: +*** Reviewed PRs and documentations for other teams: https://github.com/nus-cs2103-AY1920S2/addressbook-level3/pull/104[F09-2], https://github.com/nus-cs2103-AY1920S2/addressbook-level3/pull/42[F10-2], https://github.com/nus-cs2103-AY1920S2/addressbook-level3/pull/19[T09-1] + +<<< + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=addc] + +include::../UserGuide.adoc[tag=editc] + +//include::../UserGuide.adoc[tag=clearc] + +include::../UserGuide.adoc[tag=deletec] + +//include::../UserGuide.adoc[tag=findc] + +//include::../UserGuide.adoc[tag=listc] + +include::../UserGuide.adoc[tag=lowlimit] + +include::../UserGuide.adoc[tag=lowstockproducts] + +//include::../UserGuide.adoc[tag=receivenotif] + +include::../UserGuide.adoc[tag=reuseinputs] + +<<< + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=lowlimitfeature] + +include::../DeveloperGuide.adoc[tag=notificationWindow] + +include::../DeveloperGuide.adoc[tag=sortProductList] diff --git a/docs/team/yingxuh.adoc b/docs/team/yingxuh.adoc new file mode 100644 index 00000000000..ca1c5f948ad --- /dev/null +++ b/docs/team/yingxuh.adoc @@ -0,0 +1,172 @@ += He Yingxu - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: iTrackPro + +--- + +== Overview + +iTrack Pro is for the grocery shop owners to keep track of the products, customers, and transactions of the shop and have a better understanding of the business by viewing the performance of products and behaviors of customers. + +== Summary of contributions + +* *Major enhancement*: +1. Added *the ability to manage transcations*, e.g., *addt*, *editt*, *findt*, *listt*, *undot*, *cleart*. +** What it does: It records, edits, lists, finds, deletes transactions from the system. The quantity and sales attribute of products will be affected as well. +** Justification: This feature allows the customer to manage a large amount of transactions. It improves the usability of the application and increases the efficiency for the user to manage inventory system. +** Highlights: Adding/editting/undoing transactions will also update the quantity/sales attributes of related products. A future date time in the future is not allowed. +** Credits: These operations are inspired from the operations of persons in AB3 system. + +2. Added the *ability to plot the quantity sold of a product in a certain time range*. +** What it does: The command fetches all transactions related with the specified product in a certain time period, then +converts the data to daily time series format and plot it in a bar chart. +** Justification: This feature allows users to understand the popularity of a product visually in a trend format, such that they +could make decisions of whether to stock up or down the product. +** Highlights: If a time range is not specified, the command will consider the last 7 days by default. An end date before start date or date in the future is not allowed. + +3. Added the histogram of products to the statistics panel. +** What is does: It fetches the quantity and sales of all the products and group them by the amount of quantity/sales. +The data is then plot in a line chart format. +** Justification: It allows the user to understand whether the business is healthy, E.g. whether most of the products have sufficient inventory, is there any super-sales-creator or all the products share the same sales evenly. +** Highlights: They are displayed upon the application is launched. The graph will be updated everytime a user takes any action, e.g. adding a new product. + +* *Minor enhancement*: +** Update the UI from three side-by-side lists to tab panes with transactions representated by rows in a table (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/186[#186]). +** Split the original shared quantity class to TransactionQuantity and ProductQuantity with different minimal values. Merged sales and money class (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/117[#117], https://github.com/AY1920S2-CS2103-T09-2/main/pull/192[#192]). +** Set default dateTime and money for transactions (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/117[#117]). +** Added test for transaction model, logic, and test utils (PR https://github.com/AY1920S2-CS2103-T09-2/main/pull/209[#209], https://github.com/AY1920S2-CS2103-T09-2/main/pull/201[#201], https://github.com/AY1920S2-CS2103-T09-2/main/pull/197[#197], +https://github.com/AY1920S2-CS2103-T09-2/main/pull/183[#183], https://github.com/AY1920S2-CS2103-T09-2/main/pull/140[#140]). + +* *Code contributed*: https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=YingxuH&tabRepo=AY1920S2-CS2103-T09-2%2Fmain%5Bmaster%5D[tp-dashboard] + +** Model: [https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/model/transaction[transaction], +https://github.com/AY1920S2-CS2103-T09-2/main/blob/master/src/main/java/seedu/address/model/util/Quantity.java[util: quantity]] +** Logic: [https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/logic/commands/transaction[command: transaction], +https://github.com/AY1920S2-CS2103-T09-2/main/blob/master/src/main/java/seedu/address/logic/commands/product/PlotSalesCommand.java[command: PlotSales], +https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/logic/parser/transaction[parser: transaction], +https://github.com/AY1920S2-CS2103-T09-2/main/blob/master/src/main/java/seedu/address/logic/parser/product/PlotSalesCommandParser.java[parser: PlotSales]] +** [https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/storage/transaction[storage: tranasction], +[https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/main/java/seedu/address/ui/transaction[ui: transaction], +https://github.com/AY1920S2-CS2103-T09-2/main/blob/master/src/main/java/seedu/address/ui/PlotWindow.java[ui: PlotWindow]] +** Test: [https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/test/java/seedu/address/model/transaction[model: transaction], +https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/test/java/seedu/address/testutil/transaction[testutil: transaction], +https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/test/java/seedu/address/logic/commands/transaction[commands: transaction], +https://github.com/AY1920S2-CS2103-T09-2/main/tree/master/src/test/java/seedu/address/logic/parser/transaction[parser: transaction], +] + +* *Other contributions*: + +** Project management: +*** Set up the issue labels and milestones with my team mates. +*** Set up `netlify`, `coverall`, auto-build tools (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/17[#17]) +*** Merge PRs created by my team mates. +** Enhancements to existing features: +*** Refactored the process of triggering notification windows as passing signals to command result and making the `MainWindow` to handle the plot. (Commit https://github.com/AY1920S2-CS2103-T09-2/main/pull/192/commits/803fa013d1d0adc44434a7838fb45a145cf394bd[803fa01] of PR https://github.com/AY1920S2-CS2103-T09-2/main/pull/192[#192]) +*** Refactored the process of checking duplicate input attributes (this https://github.com/AY1920S2-CS2103-T09-2/main/pull/209/commits/c841de096b6cfb0877416062adaa5770d868bb2c[commit] from PR https://github.com/AY1920S2-CS2103-T09-2/main/pull/209[#209]) +*** Fixed the bug of re-listing all products everytime adding a transaction, allowing users to input future time, and statistics window not updated after each action (PR https://github.com/AY1920S2-CS2103-T09-2/main/pull/182[#182], https://github.com/AY1920S2-CS2103-T09-2/main/pull/197[#197], https://github.com/AY1920S2-CS2103-T09-2/main/pull/134[#134], https://github.com/AY1920S2-CS2103-T09-2/main/pull/186[#186]) +** Documentation: +*** Updated the readme from the template of AB3 to customized iTrackPro page (PR https://github.com/AY1920S2-CS2103-T09-2/main/pull/12[#12]) +*** Updated the UG with add/edit/delete/find/list transactions as well as top-selling product, revenue, and low inventory (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/27[#27]) +*** Updated the UG about the plotsales command, renaming `deletet` to `undot`, re-phrase some explanations (Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/135[#135], Pull request https://github.com/AY1920S2-CS2103-T09-2/main/pull/137[#137], https://github.com/AY1920S2-CS2103-T09-2/main/pull/212[#212]) +*** Updated the Dev Guide with NFR, Glossary, managing transaction and plot sales command. (PR https://github.com/AY1920S2-CS2103-T09-2/main/pull/21[#21], https://github.com/AY1920S2-CS2103-T09-2/main/pull/109[#109], https://github.com/AY1920S2-CS2103-T09-2/main/pull/107[#107], https://github.com/AY1920S2-CS2103-T09-2/main/pull/212[#212]) +** Community: +*** Contributed to forum discussions (examples: https://github.com/nus-cs2103-AY1920S2/forum/issues/58[#58]) +*** Reviewed the PRs and documentations from other teams in the tutorial together with my team mates(examples: https://github.com/nus-cs2103-AY1920S2/addressbook-level3/pull/104[#104], https://github.com/nus-cs2103-AY1920S2/addressbook-level3/pull/42[#42]) + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== +include::../UserGuide.adoc[tag=help] + +include::../UserGuide.adoc[tag=addt] + +include::../UserGuide.adoc[tag=listt] + +include::../UserGuide.adoc[tag=editt] + +include::../UserGuide.adoc[tag=findt] + +include::../UserGuide.adoc[tag=cleart] + +== Command Summary + +* *Adding customer* : `addc n/NAME p/PHONE_NUMBER [e/EMAIL] [a/ADDRESS] [t/TAG]…` + +e.g. `addc n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` +* *Listing all customers* : `listc` +* *Editing customer information* : `editc INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` + +e.g. `editc 1 p/91234567 e/johndoe@example.com` +* *Finding customers* : `findc [n/NAME [NAME]...] [p/PHONE] [e/EMAIL] [a/ADDRESS [ADDRESS]...]` + +e.g. `findc n/John` +* *Deleting a customer* : `deletec INDEX` + +e.g. `findc n/Betsy` + +`deletec 1` +* *Clearing all customers* : `clearc` +* *Adding a product* : `addp d/DESCRIPTION pr/PRICE q/QUANTITY cp/COSTPRICE [s/SALES]` + +e.g. `addp d/iphone x pr/1000 cp/800 q/10` +* *Listing all products* : `listp` +* *Editing a product* : `editp INDEX [d/DESCRIPTION] [pr/PRICE] [cp/COSTPRICE] [q/QUANTITY] [s/SALES]` + +e.g. `editp 1 pr/1150 q/80` +* *Finding products* : `findp KEYWORD [KEYWORD]...` + +e.g. `findp black` +* *Deleting a product* : `deletep INDEX` + +e.g. `listp` + +`deletep 2` +* *Clearing all products* : `clearp` +* *Adding a transaction* : `addt p/PRODUCT_ID c/CUSTOMER_ID dt/DATE_TIME m/MONEY q/QUANTITY [d/DESCRIPTION]` + +e.g. `addt p/20 c/2 dt/2020-02-20 10:00 m/30 q/10 d/under discount` +* *Listing all transactions* : `listt` +* *Editing a transaction* : `editt INDEX [p/PRODUCT_ID] [c/CUSTOMER_ID] [dt/DATE_TIME] [q/QUANTITY] [m/MONEY] [d/DESCRIPTION]` + +e.g. `editt 1 p/101 c/123` +* *Finding transactions* : `findt [p/PRODUCT_NAME [PRODUCT_NAME]...] [c/CUSTOMER_NAME [CUSTOMER_NAME]...] [dt/DATE_TIME] [m/MONEY]` + +e.g. `findt c/bob dt/2020-02-07 10:00` +* *Undo a transaction* : `undot INDEX` + +e.g. `findt dt/2020-01-03 10:00` + +`undot 1` +* *Clearing all transactions* : `cleart` +* *Get the revenue made in a certain period* : `revenue sd/START_DATE ed/END_DATE` + +e.g. `revenue sd/2020-01-01 10:00 ed/2020-12-12 10:01` +* *Get the profit made in a certain period* : `profit sd/START_DATE ed/END_DATE` + +e.g. `profit sd/2020-01-01 10:00 ed/2020-12-12 10:01` +* *Setting the low-inventory threshold* : `lowlimit p/PRODUCT_ID t/THRESHOLD` + +e.g. `lowlimit p/1 t/20` +* *Predicting the sales for the next month* : `predict` +* *Plotting sales* : `plotsales PRODUCT_INDEX [sd/START_DATE] [ed/END_DATE]` + +e.g. `plotsales 1 sd/2020-03-20 10:00 ed/2020-03-30 10:00` +* *Exiting from the program* : `exit` +* *Get help* : `help` + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=managetransaction] + +include::../DeveloperGuide.adoc[tag=plotsalesfeature] + +[discrete] +=== Use case: UC09 - Plot the quantity sold of a product (for products) + +*MSS* + +1. User enters the index of the product as well as the start date and end date for ploting purposes. + ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The index is out of boundary. ++ +[none] +** 1a1. The app shows an error message. ++ +Use case ends. + diff --git a/docs/tutorials/AddRemark.adoc b/docs/tutorials/AddRemark.adoc index 51044c36494..ccfa8fcd0cb 100644 --- a/docs/tutorials/AddRemark.adoc +++ b/docs/tutorials/AddRemark.adoc @@ -41,7 +41,7 @@ package seedu.address.logic.commands; import seedu.address.model.Model; /** - * Changes the remark of an existing person in the address book. + * Changes the remark of an existing customer in the address book. */ public class RemarkCommand extends Command { @@ -82,8 +82,8 @@ Following the convention in other commands, we add relevant messages as constant .RemarkCommand.java [source, java] ---- - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the person identified " - + "by the index number used in the last person listing. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the customer identified " + + "by the index number used in the last customer listing. " + "Existing remark will be overwritten by the input.\n" + "Parameters: INDEX (must be a positive integer) " + "r/ [REMARK]\n" @@ -120,8 +120,8 @@ public class RemarkCommand extends Command { private final String remark; /** - * @param index of the person in the filtered person list to edit the remark - * @param remark of the person to be updated to + * @param index of the customer in the filtered customer list to edit the remark + * @param remark of the customer to be updated to */ public RemarkCommand(Index index, String remark) { requireAllNonNull(index, remark); @@ -246,12 +246,12 @@ If you are stuck, check out the sample link:https://github.com/nus-cs2103-AY1920 Now that we have all the information that we need, let's lay the groundwork for some _persistent_ changes. We achieve that by working with the `Person` model. -Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person's name). -That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person. +Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the customer's name). +That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a customer. === Add a new `Remark` class -Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. +Create a new `Remark` in `seedu.address.model.customer`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. A copy-paste and search-replace later, you should have something like link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/b7a47c50c8e5f0430d343a23d2863446b6ce9298#diff-af2f075d24dfcd333876f0fbce321f25[this]. Note how `Remark` has no constrains and thus does not require input validation. @@ -263,7 +263,7 @@ These should be relatively simple changes. == Add a placeholder element for remark to the UI -Without getting too deep into `fxml`, let's go on a 5 minute adventure to get some placeholder text to show up for each person. +Without getting too deep into `fxml`, let's go on a 5 minute adventure to get some placeholder text to show up for each customer. Simply add [source, java] @@ -326,9 +326,9 @@ Just add link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/5 [source, java] .PersonCard.java ---- -public PersonCard(Person person, int displayedIndex) { +public PersonCard(Person customer, int displayedIndex) { //... - remark.setText(person.getRemark().value); + remark.setText(customer.getRemark().value); } ---- diff --git a/docs/tutorials/RemovingFields.adoc b/docs/tutorials/RemovingFields.adoc index 5a50b6965a6..e6b3a2e6e71 100644 --- a/docs/tutorials/RemovingFields.adoc +++ b/docs/tutorials/RemovingFields.adoc @@ -24,7 +24,7 @@ Fortunately, the IntelliJ IDEA provides a robust refactoring tool that can ident Let's try to use it as much as we can. === Assisted refactoring -The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. +The `address` field in `Person` is actually an instance of the `seedu.address.model.customer.Address` class. Since removing the `Address` class will break the application, we start by identifying ``Address``'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` > `Safe Delete` through the menu. diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index e5cfb161b73..33bfd7b5e82 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -15,15 +15,15 @@ import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; +import seedu.address.model.InventorySystem; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyInventorySystem; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; +import seedu.address.storage.InventorySystemStorage; +import seedu.address.storage.JsonInventorySystemStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; @@ -48,7 +48,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing InventorySystem ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -56,7 +56,7 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); + InventorySystemStorage addressBookStorage = new JsonInventorySystemStorage(userPrefs.getAddressBookFilePath()); storage = new StorageManager(addressBookStorage, userPrefsStorage); initLogging(config); @@ -70,29 +70,34 @@ public void init() throws Exception { /** * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * The data from the sample inventory system will be used instead if {@code storage}'s inventory system + * is not found, or an empty address book will be used instead if errors occur when + * reading {@code storage}'s address book. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional inventorySystemOptional; + ReadOnlyInventorySystem initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + inventorySystemOptional = storage.readInventorySystem(); + if (!inventorySystemOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample InventorySystem"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = inventorySystemOptional.orElseGet(SampleDataUtil::getSampleInventorySystem); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty InventorySystem"); + initialData = new InventorySystem(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty InventorySystem"); + initialData = new InventorySystem(); } return new ModelManager(initialData, userPrefs); } + /** + * Starts logging information + * @param config configuration + */ private void initLogging(Config config) { LogsCenter.init(config); } @@ -151,7 +156,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty InventorySystem"); initializedPrefs = new UserPrefs(); } @@ -167,13 +172,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting InventorySystem " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping InventorySystem ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 431e7185e76..2f004721447 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -18,7 +18,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "inventorysystem.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..c3887d26948 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -5,9 +5,26 @@ */ public class Messages { - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command! Type help for program usage instructions."; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The customer index provided is invalid."; + public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d customers listed!"; + public static final String MESSAGE_DUPLICATE_PERSON = "This customer already exists in the system"; + + public static final String MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX = "The product index provided is invalid."; + public static final String MESSAGE_INVALID_PRODUCT_QUANTITY = "There are only %1$d %2$s(s) left!"; + public static final String MESSAGE_ZERO_PRODUCT_QUANTITY = "There are no %2$ss left!"; + public static final String MESSAGE_PRODUCTS_LISTED_OVERVIEW = "%1$d products listed!"; + public static final String MESSAGE_DUPLICATE_PRODUCT = "This product already exists in the system"; + public static final String MESSAGE_INVALID_INITIAL_QUANTITY = "You cannot add an product with 0 quantity!"; + + public static final String MESSAGE_TRANSACTIONS_LISTED_OVERVIEW = "%1$d transactions listed!"; + public static final String MESSAGE_INVALID_TRANSACTION_DISPLAYED_INDEX = + "The transaction index provided is invalid."; + public static final String MESSAGE_DUPLICATE_TRANSACTION = "This transaction already exists in the system"; + + public static final String MESSAGE_INVALID_THRESHOLD_AMOUNT = "Threshold amount must be more than 0!"; + + public static final String MESSAGE_MULTIPLE_SAME_PREFIX = "Please only use each prefix once! \n%1$s"; } diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java index 19536439c09..ae0d21aa2ac 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -15,7 +15,7 @@ public class Index { * Index can only be created by calling {@link Index#fromZeroBased(int)} or * {@link Index#fromOneBased(int)}. */ - private Index(int zeroBasedIndex) { + public Index(int zeroBasedIndex) { if (zeroBasedIndex < 0) { throw new IndexOutOfBoundsException(); } diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..80fc036b362 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -28,6 +28,7 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { requireNonNull(word); String preppedWord = word.trim(); + System.out.println(sentence + " " + word); checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..a4a4b63ca4c 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -7,8 +7,10 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.ReadOnlyInventorySystem; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; /** * API of the Logic component @@ -24,19 +26,25 @@ public interface Logic { CommandResult execute(String commandText) throws CommandException, ParseException; /** - * Returns the AddressBook. + * Returns the InventorySystem. * - * @see seedu.address.model.Model#getAddressBook() + * @see seedu.address.model.Model#getInventorySystem() */ - ReadOnlyAddressBook getAddressBook(); + ReadOnlyInventorySystem getInventorySystem(); /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + ObservableList getFilteredCustomerList(); + + /** Returns an unmodifiable view of the filtered list of products */ + ObservableList getFilteredProductList(); + + /** Returns an unmodifiable view of the filtered list of transactions */ + ObservableList getFilteredTransactionList(); /** - * Returns the user prefs' address book file path. + * Returns the user prefs' inventory system file path. */ - Path getAddressBookFilePath(); + Path getInventorySystemFilePath(); /** * Returns the user prefs' GUI settings. diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index d47ce874b1a..3cc72e69b6e 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -10,11 +10,13 @@ import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.InventorySystemParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.ReadOnlyInventorySystem; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; import seedu.address.storage.Storage; /** @@ -26,12 +28,12 @@ public class LogicManager implements Logic { private final Model model; private final Storage storage; - private final AddressBookParser addressBookParser; + private final InventorySystemParser inventorySystemParser; public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + inventorySystemParser = new InventorySystemParser(); } @Override @@ -39,11 +41,11 @@ public CommandResult execute(String commandText) throws CommandException, ParseE logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); + Command command = inventorySystemParser.parseCommand(commandText); commandResult = command.execute(model); try { - storage.saveAddressBook(model.getAddressBook()); + storage.saveInventorySystem(model.getInventorySystem()); } catch (IOException ioe) { throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); } @@ -52,18 +54,28 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); + public ReadOnlyInventorySystem getInventorySystem() { + return model.getInventorySystem(); } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredCustomerList() { + return model.getFilteredCustomerList(); } @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); + public ObservableList getFilteredProductList() { + return model.getFilteredProductList(); + } + + @Override + public ObservableList getFilteredTransactionList() { + return model.getFilteredTransactionList(); + } + + @Override + public Path getInventorySystemFilePath() { + return model.getInventorySystemFilePath(); } @Override diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 92f900b7916..565cf3bf8d9 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -4,6 +4,9 @@ import java.util.Objects; +import javafx.scene.chart.XYChart; +import seedu.address.model.product.Product; + /** * Represents the result of a command execution. */ @@ -11,18 +14,37 @@ public class CommandResult { private final String feedbackToUser; + private final XYChart.Series dataSeries; + + private final Product product; + + /** Title of the plot. */ + private final String title; + /** Help information should be shown to the user. */ private final boolean showHelp; + /** Notification window should be displayed to the user. */ + private final boolean showNotification; + + /** Plot should be displayed to the user */ + private final boolean showPlot; + /** The application should exit. */ private final boolean exit; /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, XYChart.Series dataSeries, Product product, + String title, boolean showHelp, boolean showNotification, boolean showPlot, boolean exit) { this.feedbackToUser = requireNonNull(feedbackToUser); + this.dataSeries = dataSeries; + this.product = product; + this.title = title; this.showHelp = showHelp; + this.showNotification = showNotification; + this.showPlot = showPlot; this.exit = exit; } @@ -31,13 +53,33 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, null, null, "", false, false, false, false); } public String getFeedbackToUser() { return feedbackToUser; } + public XYChart.Series getDataSeries() { + return dataSeries; + } + + public String getTitle() { + return title; + } + + public Product getNotificationData() { + return product; + } + + public boolean isShowPlot() { + return showPlot; + } + + public boolean isShowNotification() { + return showNotification; + } + public boolean isShowHelp() { return showHelp; } @@ -67,5 +109,4 @@ public boolean equals(Object other) { public int hashCode() { return Objects.hash(feedbackToUser, showHelp, exit); } - } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 02fd256acba..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 7e36114902f..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,226 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..4181a28367b 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -13,7 +13,14 @@ public class ExitCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, + null, + null, + "", + false, + false, + false, + true); } } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..76741fbec32 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -16,6 +16,13 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, + null, + null, + "", + true, + false, + false, + false); } } diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/customer/AddCustomerCommand.java similarity index 54% rename from src/main/java/seedu/address/logic/commands/AddCommand.java rename to src/main/java/seedu/address/logic/commands/customer/AddCustomerCommand.java index 71656d7c5c8..54e35e17f00 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/customer/AddCustomerCommand.java @@ -1,29 +1,32 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.customer; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_PERSON; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.customer.Customer; /** - * Adds a person to the address book. + * Adds a customer to the address book. */ -public class AddCommand extends Command { +public class AddCustomerCommand extends Command { - public static final String COMMAND_WORD = "add"; + public static final String COMMAND_WORD = "addc"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a customer to the address book. \n" + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " @@ -33,23 +36,29 @@ public class AddCommand extends Command { + PREFIX_TAG + "friends " + PREFIX_TAG + "owesMoney"; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_SUCCESS = "New customer added: %1$s"; + public static final String MESSAGE_MAX_NUMBER_OF_TAGS = "Only a maximum of 5 tags are allowed"; - private final Person toAdd; + public static final int MAX_NUMBER_OF_TAGS = 5; + + private final Customer toAdd; /** - * Creates an AddCommand to add the specified {@code Person} + * Creates an AddCustomerCommand to add the specified {@code Customer} */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; + public AddCustomerCommand(Customer customer) { + requireNonNull(customer); + toAdd = customer; } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); + if (toAdd.getTags().size() > MAX_NUMBER_OF_TAGS) { + throw new CommandException(MESSAGE_MAX_NUMBER_OF_TAGS); + } + if (model.hasPerson(toAdd)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } @@ -61,7 +70,7 @@ public CommandResult execute(Model model) throws CommandException { @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); + || (other instanceof AddCustomerCommand // instanceof handles nulls + && toAdd.equals(((AddCustomerCommand) other).toAdd)); } } diff --git a/src/main/java/seedu/address/logic/commands/customer/ClearCustomerCommand.java b/src/main/java/seedu/address/logic/commands/customer/ClearCustomerCommand.java new file mode 100644 index 00000000000..4bb2d30d27c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/customer/ClearCustomerCommand.java @@ -0,0 +1,25 @@ +package seedu.address.logic.commands.customer; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.InventorySystem; +import seedu.address.model.Model; + +/** + * Clears the address book. + */ +public class ClearCustomerCommand extends Command { + + public static final String COMMAND_WORD = "clearc"; + + public static final String MESSAGE_SUCCESS = "Customer list has been cleared!"; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.setInventorySystem(new InventorySystem(), COMMAND_WORD); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/customer/DeleteCustomerCommand.java b/src/main/java/seedu/address/logic/commands/customer/DeleteCustomerCommand.java new file mode 100644 index 00000000000..c7f4ce9ae13 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/customer/DeleteCustomerCommand.java @@ -0,0 +1,78 @@ +package seedu.address.logic.commands.customer; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.customer.Customer; +import seedu.address.model.transaction.Transaction; + +/** + * Deletes a customer identified using it's displayed index from the address book. + */ +public class DeleteCustomerCommand extends Command { + + public static final String COMMAND_WORD = "deletec"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the customer identified by the index number used in the displayed customer list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Customer: %1$s"; + + private final Index targetIndex; + + public DeleteCustomerCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + // delete customer + List lastShownList = model.getFilteredCustomerList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Customer customerToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deletePerson(customerToDelete); + + // remove transactions with deleted customer + updateTransactionList(model, customerToDelete); + + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, customerToDelete)); + } + + /** + * Deletes transactions where the customer is the customer to be deleted. + * @param model + * @param customerToDelete + */ + private void updateTransactionList(Model model, Customer customerToDelete) { + List transactions = model.getInventorySystem().getTransactionList(); + + for (int i = 0; i < transactions.size(); i++) { + Transaction transaction = transactions.get(i); + if (transaction.getCustomer().equals(customerToDelete)) { + model.deleteTransaction(transaction); + } + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteCustomerCommand // instanceof handles nulls + && targetIndex.equals(((DeleteCustomerCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/customer/EditCustomerCommand.java b/src/main/java/seedu/address/logic/commands/customer/EditCustomerCommand.java new file mode 100644 index 00000000000..710643a34c9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/customer/EditCustomerCommand.java @@ -0,0 +1,312 @@ +package seedu.address.logic.commands.customer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_PERSON; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.customer.Address; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; +import seedu.address.model.tag.Tag; +import seedu.address.model.transaction.Transaction; + +/** + * Edits the details of an existing customer in the address book. + */ +public class EditCustomerCommand extends Command { + + public static final String COMMAND_WORD = "editc"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the customer identified " + + "by the index number used in the displayed customer list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PHONE + "91234567 " + + PREFIX_EMAIL + "johndoe@example.com"; + + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Customer: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_MAX_NUMBER_OF_TAGS = "Only a maximum of 5 tags are allowed"; + + public static final int MAX_NUMBER_OF_TAGS = 5; + + private final Index index; + private final EditCustomerDescriptor editCustomerDescriptor; + + /** + * @param index of the customer in the filtered customer list to edit + * @param editCustomerDescriptor details to edit the customer with + */ + public EditCustomerCommand(Index index, EditCustomerDescriptor editCustomerDescriptor) { + requireNonNull(index); + requireNonNull(editCustomerDescriptor); + + this.index = index; + this.editCustomerDescriptor = new EditCustomerDescriptor(editCustomerDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + // create edited customer + List lastShownList = model.getFilteredCustomerList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Customer customerToEdit = lastShownList.get(index.getZeroBased()); + Customer editedCustomer = createEditedPerson(customerToEdit, editCustomerDescriptor); + + if (editedCustomer.getTags().size() > MAX_NUMBER_OF_TAGS) { + throw new CommandException(MESSAGE_MAX_NUMBER_OF_TAGS); + } + + if (modelHasDuplicateCustomer(model, editedCustomer)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + // update customer list + model.setPerson(customerToEdit, editedCustomer); + model.updateFilteredCustomerList(PREDICATE_SHOW_ALL_PERSONS); + + // update transactions with customer info + updateTransactionList(model, editedCustomer); + + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedCustomer)); + } + + /** + * Check whether model has duplicate customer + * @param model + * @param editedCustomer + * @return true if model has duplicate customer, else false + */ + private boolean modelHasDuplicateCustomer(Model model, Customer editedCustomer) { + List customers = model.getInventorySystem().getPersonList(); + for (int i = 0; i < customers.size(); i++) { + Customer customer = customers.get(i); + if (customer.getId() != editedCustomer.getId()) { + if (customer.equals(editedCustomer)) { + return true; + } + } + } + return false; + } + + /** + * Update transaction list with new customer info. + * @param model + * @param editedCustomer + */ + private void updateTransactionList(Model model, Customer editedCustomer) { + List transactions = model.getInventorySystem().getTransactionList(); + + for (int i = 0; i < transactions.size(); i++) { + Transaction transaction = transactions.get(i); + Transaction newTransaction = transaction; + if (editedCustomer.getId().equals(transaction.getCustomerId())) { + newTransaction = new Transaction(editedCustomer, + transaction.getProduct(), + transaction.getCustomerId(), + transaction.getProductId(), + transaction.getDateTime(), + transaction.getQuantity(), + transaction.getMoney(), + transaction.getDescription()); + } + model.setTransaction(transaction, newTransaction); + } + } + + /** + * Creates and returns a {@code Customer} with the details of {@code customerToEdit} + * edited with {@code editCustomerDescriptor}. + */ + private static Customer createEditedPerson(Customer customerToEdit, EditCustomerDescriptor editCustomerDescriptor) { + assert customerToEdit != null; + + UUID id = customerToEdit.getId(); + Name updatedName = editCustomerDescriptor.getName().orElse(customerToEdit.getName()); + Phone updatedPhone = editCustomerDescriptor.getPhone().orElse(customerToEdit.getPhone()); + Email updatedEmail = editCustomerDescriptor.getEmail().orElse(customerToEdit.getEmail()); + Address updatedAddress = editCustomerDescriptor.getAddress().orElse(customerToEdit.getAddress()); + Set updatedTags = editCustomerDescriptor.getTags().orElse(customerToEdit.getTags()); + + return new Customer(id, updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditCustomerCommand)) { + return false; + } + + // state check + EditCustomerCommand e = (EditCustomerCommand) other; + return index.equals(e.index) + && editCustomerDescriptor.equals(e.editCustomerDescriptor); + } + + /** + * Stores the details to edit the customer with. Each non-empty field value will replace the + * corresponding field value of the customer. + */ + public static class EditCustomerDescriptor { + private UUID id; + private Name name; + private Phone phone; + private Email email; + private Address address; + private Set tags; + + public EditCustomerDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditCustomerDescriptor(EditCustomerDescriptor toCopy) { + setId(toCopy.id); + setName(toCopy.name); + setPhone(toCopy.phone); + setEmail(toCopy.email); + setAddress(toCopy.address); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + } + + public void setId(UUID id) { + this.id = id; + } + + public Optional getId() { + return Optional.ofNullable(id); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + + public void setAddress(Address address) { + this.address = address; + } + + public Optional
getAddress() { + return Optional.ofNullable(address); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditCustomerDescriptor)) { + return false; + } + + // state check + EditCustomerDescriptor e = (EditCustomerDescriptor) other; + + return getId().equals(e.getId()) + && getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getAddress().equals(e.getAddress()) + && getTags().equals(e.getTags()); + } + + @Override + public String toString() { + return "EditCustomerDescriptor{" + + "id=" + id + + ", name=" + + name + ", phone=" + + phone + ", email=" + + email + ", address=" + + address + ", tags=" + + tags + '}'; + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/customer/FindCustomerCommand.java b/src/main/java/seedu/address/logic/commands/customer/FindCustomerCommand.java new file mode 100644 index 00000000000..fb98a561691 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/customer/FindCustomerCommand.java @@ -0,0 +1,59 @@ +package seedu.address.logic.commands.customer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.customer.Customer; + +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindCustomerCommand extends Command { + + public static final String COMMAND_WORD = "findc"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose attributes (n/ for name, " + + "a/ for address, p/ for phone number, e/ for email address) contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: " + + "[" + PREFIX_NAME + "NAME [NAME]...] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_ADDRESS + "ADDRESS [ADDRESS]...] " + + "[" + PREFIX_EMAIL + "EMAIL]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "alice bob charlie"; + + private final Predicate predicate; + + public FindCustomerCommand(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredCustomerList(predicate); + if (model.getFilteredCustomerList().size() == 0) { + return new CommandResult(predicate.toString()); + } + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredCustomerList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindCustomerCommand // instanceof handles nulls + && predicate.equals(((FindCustomerCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/customer/ListCustomerCommand.java b/src/main/java/seedu/address/logic/commands/customer/ListCustomerCommand.java new file mode 100644 index 00000000000..812f8250cd5 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/customer/ListCustomerCommand.java @@ -0,0 +1,30 @@ +package seedu.address.logic.commands.customer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all customers in the address book to the user. + */ +public class ListCustomerCommand extends Command { + + public static final String COMMAND_WORD = "listc"; + + public static final String MESSAGE_SUCCESS = "Listed all customers"; + public static final String MESSAGE_EMPTY = "There are no customers in the list!"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredCustomerList(PREDICATE_SHOW_ALL_PERSONS); + if (model.getFilteredCustomerList().size() == 0) { + return new CommandResult(MESSAGE_EMPTY); + } + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/product/AddProductCommand.java b/src/main/java/seedu/address/logic/commands/product/AddProductCommand.java new file mode 100644 index 00000000000..3de2754d3b1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/product/AddProductCommand.java @@ -0,0 +1,69 @@ +package seedu.address.logic.commands.product; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_PRODUCT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COSTPRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_QUANTITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALES; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.product.Product; + +/** + * Adds a product to the product list. + */ +public class AddProductCommand extends Command { + + public static final String COMMAND_WORD = "addp"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a product to the product list.\n" + + "Parameters: " + + PREFIX_DESCRIPTION + "DESCRIPTION " + + PREFIX_COSTPRICE + "COST PRICE " + + PREFIX_PRICE + "PRICE " + + PREFIX_QUANTITY + "QUANTITY " + + "[" + PREFIX_SALES + "SALES] \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_DESCRIPTION + "iphone " + + PREFIX_COSTPRICE + "400 " + + PREFIX_PRICE + "1000 " + + PREFIX_QUANTITY + "10 " + + PREFIX_SALES + "100 "; + + public static final String MESSAGE_SUCCESS = "New product added: %1$s"; + + private final Product toAdd; + + /** + * Creates an AddProductCommand to add the specified {@code Product} + */ + public AddProductCommand(Product product) { + requireNonNull(product); + toAdd = product; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasProduct(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PRODUCT); + } + + model.addProduct(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddProductCommand // instanceof handles nulls + && toAdd.equals(((AddProductCommand) other).toAdd)); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/product/ClearProductCommand.java b/src/main/java/seedu/address/logic/commands/product/ClearProductCommand.java new file mode 100644 index 00000000000..2505f3f0804 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/product/ClearProductCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.commands.product; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.InventorySystem; +import seedu.address.model.Model; + +/** + * Clears the product list. + */ +public class ClearProductCommand extends Command { + + public static final String COMMAND_WORD = "clearp"; + public static final String MESSAGE_SUCCESS = "Product list has been cleared!"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.setInventorySystem(new InventorySystem(), COMMAND_WORD); + return new CommandResult(MESSAGE_SUCCESS); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/product/DeleteProductCommand.java b/src/main/java/seedu/address/logic/commands/product/DeleteProductCommand.java new file mode 100644 index 00000000000..db0495b9043 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/product/DeleteProductCommand.java @@ -0,0 +1,78 @@ +package seedu.address.logic.commands.product; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; + +/** + * Deletes a product identified using it's displayed index from the product list. + */ +public class DeleteProductCommand extends Command { + + public static final String COMMAND_WORD = "deletep"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the product identified by the index number used in the displayed product list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_PRODUCT_SUCCESS = "Deleted Product: %1$s"; + + private final Index targetIndex; + + public DeleteProductCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + // delete product + List lastShownList = model.getFilteredProductList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX); + } + + Product productToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteProduct(productToDelete); + + // remove transactions with deleted product + updateTransactionList(model, productToDelete); + + return new CommandResult(String.format(MESSAGE_DELETE_PRODUCT_SUCCESS, productToDelete)); + } + + /** + * Deletes transactions where the product is the product to be deleted. + * @param model + * @param productToDelete + */ + private void updateTransactionList(Model model, Product productToDelete) { + List transactions = model.getInventorySystem().getTransactionList(); + + for (int i = 0; i < transactions.size(); i++) { + Transaction transaction = transactions.get(i); + if (transaction.getProduct().equals(productToDelete)) { + model.deleteTransaction(transaction); + } + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteProductCommand // instanceof handles nulls + && targetIndex.equals(((DeleteProductCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/product/EditProductCommand.java b/src/main/java/seedu/address/logic/commands/product/EditProductCommand.java new file mode 100644 index 00000000000..e3344a6f931 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/product/EditProductCommand.java @@ -0,0 +1,320 @@ +package seedu.address.logic.commands.product; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_PRODUCT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COSTPRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_QUANTITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALES; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.product.CostPrice; +import seedu.address.model.product.Price; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; +import seedu.address.model.util.QuantityThreshold; + +/** + * Edits the details of an existing product in the address book. + */ +public class EditProductCommand extends Command { + + public static final String COMMAND_WORD = "editp"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the product identified " + + "by the index number used in the displayed product list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION] " + + "[" + PREFIX_COSTPRICE + "COST PRICE] " + + "[" + PREFIX_PRICE + "PRICE] " + + "[" + PREFIX_QUANTITY + "QUANTITY] " + + "[" + PREFIX_SALES + "SALES] \n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_DESCRIPTION + "Black watch "; + + public static final String MESSAGE_EDIT_PRODUCT_SUCCESS = "Edited Product: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + + private final Index index; + private final EditProductDescriptor editProductDescriptor; + + /** + * @param index of the product in the filtered product list to edit + * @param editProductDescriptor details to edit the product with + */ + public EditProductCommand(Index index, EditProductDescriptor editProductDescriptor) { + requireNonNull(index); + requireNonNull(editProductDescriptor); + + this.index = index; + this.editProductDescriptor = new EditProductDescriptor(editProductDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + // create edited product + List lastShownList = model.getFilteredProductList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX); + } + + Product productToEdit = lastShownList.get(index.getZeroBased()); + Product editedProduct = createEditedProduct(productToEdit, editProductDescriptor); + + if (modelHasDuplicateProduct(model, editedProduct)) { + throw new CommandException(MESSAGE_DUPLICATE_PRODUCT); + } + + // update product list + model.setProduct(productToEdit, editedProduct); + model.updateFilteredProductList(); + + // update transactions with product info + updateTransactionList(model, editedProduct); + + // display notification window if threshold is reached + if (editedProduct.reachesThreshold()) { + return new CommandResult(String.format(MESSAGE_EDIT_PRODUCT_SUCCESS, editedProduct), + null, + editedProduct, + "", + false, + true, + false, + false); + } + + return new CommandResult(String.format(MESSAGE_EDIT_PRODUCT_SUCCESS, editedProduct)); + } + + /** + * Check whether model has duplicate product + * @param model + * @param editedProduct + * @return true if model has duplicate product, else false + */ + private boolean modelHasDuplicateProduct(Model model, Product editedProduct) { + List products = model.getInventorySystem().getProductList(); + + for (int i = 0; i < products.size(); i++) { + Product product = products.get(i); + if (product.getId() != editedProduct.getId()) { + if (product.equals(editedProduct)) { + return true; + } + } + } + + return false; + } + + /** + * Update transaction list with new product info. + * @param model + * @param editedProduct + */ + private void updateTransactionList(Model model, Product editedProduct) { + List transactions = model.getInventorySystem().getTransactionList(); + + for (int i = 0; i < transactions.size(); i++) { + Transaction transaction = transactions.get(i); + Transaction newTransaction = transaction; + if (editedProduct.getId().equals(transaction.getProductId())) { + newTransaction = new Transaction(transaction.getCustomer(), + editedProduct, + transaction.getCustomerId(), + transaction.getProductId(), + transaction.getDateTime(), + transaction.getQuantity(), + transaction.getMoney(), + transaction.getDescription()); + } + model.setTransaction(transaction, newTransaction); + } + } + + /** + * Creates and returns a {@code Product} with the details of {@code productToEdit} + * edited with {@code editProductDescriptor}. + */ + public static Product createEditedProduct(Product productToEdit, EditProductDescriptor editProductDescriptor) { + assert productToEdit != null; + + UUID id = productToEdit.getId(); + Description updatedDescription = editProductDescriptor.getDescription().orElse(productToEdit.getDescription()); + CostPrice updatedCostPrice = editProductDescriptor.getCostPrice().orElse(productToEdit.getCostPrice()); + Price updatedPrice = editProductDescriptor.getPrice().orElse(productToEdit.getPrice()); + Quantity updatedQuantity = editProductDescriptor.getQuantity().orElse(productToEdit.getQuantity()); + Money updatedSales = editProductDescriptor.getMoney().orElse(productToEdit.getMoney()); + QuantityThreshold updatedThreshold = editProductDescriptor.getThreshold().orElse(productToEdit.getThreshold()); + double updatedProgress = productToEdit.getProgress(); + + return new Product(id, updatedDescription, updatedCostPrice, updatedPrice, updatedQuantity, + updatedSales, updatedThreshold, updatedProgress); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditProductCommand)) { + return false; + } + + // state check + EditProductCommand e = (EditProductCommand) other; + return index.equals(e.index) + && editProductDescriptor.equals(e.editProductDescriptor); + } + + /** + * Stores the details to edit the product with. Each non-empty field value will replace the + * corresponding field value of the product. + */ + public static class EditProductDescriptor { + private UUID id; + private Description description; + private CostPrice costPrice; + private Price price; + private Quantity quantity; + private Money sales; + private QuantityThreshold threshold; + + public EditProductDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditProductDescriptor(EditProductDescriptor toCopy) { + setId(toCopy.id); + setDescription(toCopy.description); + setCostPrice(toCopy.costPrice); + setPrice(toCopy.price); + setQuantity(toCopy.quantity); + setSales(toCopy.sales); + setThreshold(toCopy.threshold); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(description, costPrice, price, quantity, sales, threshold); + } + + public void setId(UUID id) { + this.id = id; + } + + public Optional getId() { + return Optional.ofNullable(id); + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public void setCostPrice(CostPrice costPrice) { + this.costPrice = costPrice; + } + + public Optional getCostPrice() { + return Optional.ofNullable(costPrice); + } + + public void setPrice(Price price) { + this.price = price; + } + + public Optional getPrice() { + return Optional.ofNullable(price); + } + + public void setQuantity(Quantity quantity) { + this.quantity = quantity; + } + + public Optional getQuantity() { + return Optional.ofNullable(quantity); + } + + public void setSales(Money sales) { + this.sales = sales; + } + + public Optional getMoney() { + return Optional.ofNullable(sales); + } + + public void setThreshold(QuantityThreshold threshold) { + this.threshold = threshold; + } + + public Optional getThreshold() { + return Optional.ofNullable(threshold); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditProductDescriptor)) { + return false; + } + + // state check + EditProductDescriptor e = (EditProductDescriptor) other; + + return getId().equals(e.getId()) + && getDescription().equals(e.getDescription()) + && getCostPrice().equals(e.getCostPrice()) + && getPrice().equals(e.getPrice()) + && getQuantity().equals(e.getQuantity()) + && getMoney().equals(e.getMoney()) + && getThreshold().equals(e.getThreshold()); + } + + @Override + public String toString() { + return "EditProductDescriptor{" + + "id=" + id + ", description=" + + description + ", costPrice=" + + costPrice + ", price=" + + price + ", quantity=" + + quantity + ", sales=" + + sales + ", threshold=" + + threshold + '}'; + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/product/FindProductCommand.java b/src/main/java/seedu/address/logic/commands/product/FindProductCommand.java new file mode 100644 index 00000000000..72afdd695af --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/product/FindProductCommand.java @@ -0,0 +1,44 @@ +package seedu.address.logic.commands.product; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.product.DescriptionContainsKeywordsPredicate; + +/** + * Finds and lists all products in product list whose description contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindProductCommand extends Command { + + public static final String COMMAND_WORD = "findp"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all products whose descriptions contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [KEYWORD]...\n" + + "Example: " + COMMAND_WORD + " alice bob charlie"; + + private final DescriptionContainsKeywordsPredicate predicate; + + public FindProductCommand(DescriptionContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredProductList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PRODUCTS_LISTED_OVERVIEW, model.getFilteredProductList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindProductCommand // instanceof handles nulls + && predicate.equals(((FindProductCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/product/ListProductCommand.java b/src/main/java/seedu/address/logic/commands/product/ListProductCommand.java new file mode 100644 index 00000000000..d935094c5d4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/product/ListProductCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.commands.product; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PRODUCTS; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all products in the product list to the user. + */ +public class ListProductCommand extends Command { + + public static final String COMMAND_WORD = "listp"; + + public static final String MESSAGE_SUCCESS = "Listed all products"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredProductList(PREDICATE_SHOW_ALL_PRODUCTS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/product/LowLimitCommand.java b/src/main/java/seedu/address/logic/commands/product/LowLimitCommand.java new file mode 100644 index 00000000000..c49bdffb610 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/product/LowLimitCommand.java @@ -0,0 +1,99 @@ +package seedu.address.logic.commands.product; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.product.EditProductCommand.EditProductDescriptor; +import static seedu.address.logic.commands.product.EditProductCommand.createEditedProduct; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_THRESHOLD; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.product.Product; +import seedu.address.model.util.QuantityThreshold; +import seedu.address.ui.NotificationWindow; + +/** + * Sets the product low limit threshold for notifications. + */ +public class LowLimitCommand extends Command { + + public static final String COMMAND_WORD = "lowlimit"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sets the notification threshold for products." + + " Updates the balance indicator, as the threshold represents 20% of desired quantity. \n" + + "Alerts the user when the product quantity reached the threshold set by the user. \n" + + "Parameters: " + + PREFIX_PRODUCT + "PRODUCT_INDEX " + + PREFIX_THRESHOLD + "THRESHOLD \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_PRODUCT + "1 " + + PREFIX_THRESHOLD + "20"; + + public static final String MESSAGE_SUCCESS = "New threshold set: %1$s"; + + private final Index productIndex; + private final EditProductDescriptor editProductDescriptor = new EditProductDescriptor(); + private QuantityThreshold threshold; + + public LowLimitCommand(Index productIndex, QuantityThreshold threshold) { + requireNonNull(productIndex); + requireNonNull(threshold); + + this.productIndex = productIndex; + this.threshold = threshold; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + List lastShownList = model.getFilteredProductList(); + + if (productIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX); + } + + Product productToEdit = lastShownList.get(productIndex.getZeroBased()); + int thresholdValue = threshold.value; + Product editedProduct = createEditedProduct(productToEdit, editProductDescriptor); + if (thresholdValue > 0) { + editedProduct.setThreshold(String.valueOf(thresholdValue)); + } else { + throw new CommandException(Messages.MESSAGE_INVALID_THRESHOLD_AMOUNT); + } + + // update product list + model.setProduct(productToEdit, editedProduct); + model.updateFilteredProductList(); + + // show notification if quantity < threshold + if (editedProduct.getQuantity().getValue() <= thresholdValue) { + NotificationWindow window = new NotificationWindow(); + window.show(editedProduct.getDescription(), editedProduct.getQuantity()); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, editedProduct)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof LowLimitCommand)) { + return false; + } + + // state check + LowLimitCommand e = (LowLimitCommand) other; + + return productIndex.equals(e.productIndex) && threshold.equals(e.threshold); + } +} diff --git a/src/main/java/seedu/address/logic/commands/product/PlotSalesCommand.java b/src/main/java/seedu/address/logic/commands/product/PlotSalesCommand.java new file mode 100644 index 00000000000..8cc47444ed9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/product/PlotSalesCommand.java @@ -0,0 +1,122 @@ +package seedu.address.logic.commands.product; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE; +import static seedu.address.model.transaction.DateTime.populateDates; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import javafx.scene.chart.XYChart; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.product.Product; +import seedu.address.model.product.ProductQuantity; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.DateTimeInRangePredicate; +import seedu.address.model.transaction.JointTransactionPredicate; +import seedu.address.model.transaction.ProductIdEqualsPredicate; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.util.Quantity; + +/** + * Plot sales of a product in a given period. + */ +public class PlotSalesCommand extends Command { + + public static final String COMMAND_WORD = "plotsales"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Plots the sales of product to the screen\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_START_DATE + "START DATE] " + + "[" + PREFIX_END_DATE + "END DATE] \n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_START_DATE + "2020-03-20 10:00 " + + PREFIX_END_DATE + "2020-03-30 10:00"; + + public static final String MESSAGE_SUCCESS = "Sales for product %1$s plotted."; + public static final String TITLE = "Sales of %1$s between %2$s and %3$s"; + public static final String MESSAGE_DATE_CONFLICT = "Start date must be after end date"; + public static final String MESSAGE_NO_PRODUCT_SALES = + "There are no sales for %1$s during this period of time"; + + private final Index targetIndex; + private final DateTime startDateTime; + private final DateTime endDateTime; + + public PlotSalesCommand(Index targetIndex, DateTime startDateTime, DateTime endDateTime) { + this.targetIndex = targetIndex; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List> predicates = new ArrayList<>(); + List lastShownList = model.getFilteredProductList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX); + } + + if (startDateTime.value.compareTo(endDateTime.value) > 0) { + throw new CommandException(MESSAGE_DATE_CONFLICT); + } + + Product productToPlot = lastShownList.get(targetIndex.getZeroBased()); + + predicates.add(new ProductIdEqualsPredicate(productToPlot.getId())); + predicates.add(new DateTimeInRangePredicate(startDateTime, endDateTime)); + Predicate jointPredicate = new JointTransactionPredicate(predicates); + List transactions = model.filterTransaction(jointPredicate); + + if (transactions.isEmpty()) { + throw new CommandException(String.format(MESSAGE_NO_PRODUCT_SALES, + productToPlot.getDescription().value)); + } + + XYChart.Series dataSeries = generateDataSeries(transactions); + + return new CommandResult( + String.format(MESSAGE_SUCCESS, productToPlot.getDescription()), + dataSeries, + null, + String.format(TITLE, + productToPlot.getDescription(), + startDateTime.toDateString(), + endDateTime.toDateString()), + false, + false, + true, + false); + } + + /** + * Generates data series usable to the bar chart plot. + * @param transactions a list of transactions. + */ + private XYChart.Series generateDataSeries(List transactions) { + XYChart.Series dataSeries = new XYChart.Series(); + + List dateTimes = populateDates(startDateTime, endDateTime); + dateTimes.forEach(date -> { + Quantity quantity = new ProductQuantity(0); + for (Transaction t: transactions) { + if (t.getDateTime().isOnSameDay(date)) { + quantity = quantity.plus(t.getQuantity()); + } + } + + dataSeries.getData().add(new XYChart.Data(date.toDateString(), quantity.getValue())); + }); + + return dataSeries; + } +} diff --git a/src/main/java/seedu/address/logic/commands/statistics/PredictCommand.java b/src/main/java/seedu/address/logic/commands/statistics/PredictCommand.java new file mode 100644 index 00000000000..2bac362f86a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/statistics/PredictCommand.java @@ -0,0 +1,97 @@ +package seedu.address.logic.commands.statistics; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.List; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.util.Money; + +/** + * Predicts sales for the next month based on sales in the previous three months. + */ +public class PredictCommand extends Command { + + public static final String COMMAND_WORD = "predict"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Predicts sales for the next month based on sales in the previous three months. " + + "\nExample: " + COMMAND_WORD; + + public static final String MESSAGE_SUCCESS = "Predicted sales for next month: $%1$s"; + public static final String MESSAGE_NUMBER_FORMAT = "Price of product is invalid"; + public static final String MESSAGE_NO_PRODUCTS = "At least one product is required"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.getFilteredProductList().size() == 0) { + throw new CommandException(MESSAGE_NO_PRODUCTS); + } + + Money revenue = predictRevenue(model); + + return new CommandResult(String.format(MESSAGE_SUCCESS, revenue)); + } + + /** + * Predicts the revenue for the next month based on past three months. + * @param model + * @return calculated sales + * @throws CommandException + */ + private Money predictRevenue(Model model) throws CommandException { + List transactionList = model.getFilteredTransactionList(); + LocalDate todayDate = LocalDate.now(); + + Money firstMonthRevenue = calculateRevenueForMonth(transactionList, todayDate); + Money secondMonthRevenue = calculateRevenueForMonth(transactionList, todayDate.minusMonths(1)); + Money thirdMonthRevenue = calculateRevenueForMonth(transactionList, todayDate.minusMonths(2)); + int predictedRevenue = (firstMonthRevenue.value + + secondMonthRevenue.value + + thirdMonthRevenue.value) / 3; + + return new Money(predictedRevenue); + } + + /** + * Calculate the revenue in a given month. + * @param transactionList + * @param date + * @return revenue for that month + * @throws CommandException + */ + private Money calculateRevenueForMonth(List transactionList, LocalDate date) + throws CommandException { + int revenue = 0; + for (int i = 0; i < transactionList.size(); i++) { + Transaction transaction = transactionList.get(i); + LocalDateTime transactionDateTime = transaction.getDateTime().value; + Month transactionMonth = transactionDateTime.getMonth(); + + if (transactionMonth == date.getMonth()) { + try { + int price = transaction.getMoney().value; + revenue += price; + } catch (Exception e) { + throw new CommandException(MESSAGE_NUMBER_FORMAT); + } + } + } + return new Money(revenue); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PredictCommand); // instanceof handles nulls + } +} diff --git a/src/main/java/seedu/address/logic/commands/statistics/ProfitCommand.java b/src/main/java/seedu/address/logic/commands/statistics/ProfitCommand.java new file mode 100644 index 00000000000..05202313908 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/statistics/ProfitCommand.java @@ -0,0 +1,114 @@ +package seedu.address.logic.commands.statistics; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE; + +import java.time.LocalDateTime; +import java.util.List; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.util.Money; + +/** + * Displays the profit trend in a selected period. + */ +public class ProfitCommand extends Command { + + public static final String COMMAND_WORD = "profit"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Displays the profit trend in a selected period. \n" + + "Parameters: " + + PREFIX_START_DATE + "START DATE " + + PREFIX_END_DATE + "END DATE \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_START_DATE + "2020-01-01 10:00 " + + PREFIX_END_DATE + "2020-12-12 10:01"; + + public static final String MESSAGE_SUCCESS = "Profit from %1$s to %2$s: $%3$s"; + public static final String MESSAGE_NUMBER_FORMAT = "Price/ quantity/ cost price of product is invalid"; + public static final String MESSAGE_NO_PRODUCTS = "At least one product is required"; + public static final String MESSAGE_NEGATIVE_PROFIT = "You have made a loss of $%1$s!"; + public static final String MESSAGE_DATE_CONFLICT = "Start date must be after end date"; + + private final DateTime startDateTime; + private final DateTime endDateTime; + + /** + * Creates an ProfitCommand to display the profit. + */ + public ProfitCommand(DateTime startDateTime, DateTime endDateTime) { + requireNonNull(startDateTime); + requireNonNull(endDateTime); + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.getFilteredProductList().size() == 0) { + throw new CommandException(MESSAGE_NO_PRODUCTS); + } + + if (startDateTime.value.compareTo(endDateTime.value) > 0) { + throw new CommandException(MESSAGE_DATE_CONFLICT); + } + + List transactions = model.getInventorySystem().getTransactionList(); + Money profit = calculateProfit(transactions); + + return new CommandResult(String.format(MESSAGE_SUCCESS, + startDateTime, + endDateTime, + profit)); + } + + /** + * Calculates the profit in a given time period + * @param transactions + * @return calculated profit + * @throws CommandException + */ + private Money calculateProfit(List transactions) throws CommandException { + int profit = 0; + + for (int i = 0; i < transactions.size(); i++) { + Transaction transaction = transactions.get(i); + LocalDateTime transactionDateTime = transaction.getDateTime().value; + + if (transactionDateTime.compareTo(startDateTime.value) >= 0 + && transactionDateTime.compareTo(endDateTime.value) <= 0) { + try { + int price = transaction.getMoney().value; + int quantity = transaction.getQuantity().getValue(); + int costPrice = Integer.parseInt(transaction.getProduct().getCostPrice().value); + profit += (price - costPrice * quantity); + } catch (Exception e) { + throw new CommandException(MESSAGE_NUMBER_FORMAT); + } + } + } + + if (profit < 0) { + throw new CommandException(String.format(MESSAGE_NEGATIVE_PROFIT, -profit)); + } + + return new Money(profit); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ProfitCommand // instanceof handles nulls + && startDateTime.equals(((ProfitCommand) other).startDateTime) + && endDateTime.equals(((ProfitCommand) other).endDateTime)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/statistics/RevenueCommand.java b/src/main/java/seedu/address/logic/commands/statistics/RevenueCommand.java new file mode 100644 index 00000000000..de313bda4c8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/statistics/RevenueCommand.java @@ -0,0 +1,107 @@ +package seedu.address.logic.commands.statistics; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE; + +import java.time.LocalDateTime; +import java.util.List; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.util.Money; + +/** + * Displays the revenue trend in a selected period. + */ +public class RevenueCommand extends Command { + + public static final String COMMAND_WORD = "revenue"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Displays the revenue trend in a selected period. \n" + + "Parameters: " + + PREFIX_START_DATE + "START DATE " + + PREFIX_END_DATE + "END DATE \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_START_DATE + "2020-01-01 10:00 " + + PREFIX_END_DATE + "2020-12-12 10:01"; + + public static final String MESSAGE_SUCCESS = "Revenue from %1$s to %2$s: $%3$s"; + public static final String MESSAGE_NUMBER_FORMAT = "Price of product is invalid"; + public static final String MESSAGE_NO_PRODUCTS = "At least one product is required"; + public static final String MESSAGE_DATE_CONFLICT = "Start date must be after end date"; + + private final DateTime startDateTime; + private final DateTime endDateTime; + + /** + * Creates an RevenueCommand to display the revenue. + */ + public RevenueCommand(DateTime startDateTime, DateTime endDateTime) { + requireNonNull(startDateTime); + requireNonNull(endDateTime); + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.getFilteredProductList().size() == 0) { + throw new CommandException(MESSAGE_NO_PRODUCTS); + } + + if (startDateTime.value.compareTo(endDateTime.value) > 0) { + throw new CommandException(MESSAGE_DATE_CONFLICT); + } + + List transactions = model.getInventorySystem().getTransactionList(); + Money revenue = calculateRevenue(transactions); + + return new CommandResult(String.format(MESSAGE_SUCCESS, + startDateTime, + endDateTime, + revenue)); + } + + /** + * Calculates the revenue in a given time period + * @param transactions + * @return calculated sales + * @throws CommandException + */ + private Money calculateRevenue(List transactions) throws CommandException { + int revenue = 0; + + for (int i = 0; i < transactions.size(); i++) { + Transaction transaction = transactions.get(i); + LocalDateTime transactionDateTime = transaction.getDateTime().value; + + if (transactionDateTime.compareTo(startDateTime.value) > 0 + && transactionDateTime.compareTo(endDateTime.value) < 0) { + try { + int price = transaction.getMoney().value; + revenue += price; + } catch (Exception e) { + throw new CommandException(MESSAGE_NUMBER_FORMAT); + } + } + } + + return new Money(revenue); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RevenueCommand // instanceof handles nulls + && startDateTime.equals(((RevenueCommand) other).startDateTime) + && endDateTime.equals(((RevenueCommand) other).endDateTime)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/transaction/AddTransactionCommand.java b/src/main/java/seedu/address/logic/commands/transaction/AddTransactionCommand.java new file mode 100644 index 00000000000..ae19f0910a5 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/transaction/AddTransactionCommand.java @@ -0,0 +1,157 @@ +package seedu.address.logic.commands.transaction; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_PRODUCT; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_TRANSACTION; +import static seedu.address.logic.commands.product.EditProductCommand.EditProductDescriptor; +import static seedu.address.logic.commands.product.EditProductCommand.createEditedProduct; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MONEY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_QUANTITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TRANS_DESCRIPTION; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.transaction.TransactionFactory; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; + +/** + * Adds a transaction to the system. + */ +public class AddTransactionCommand extends Command { + + public static final String COMMAND_WORD = "addt"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a transaction to the application. \n" + + "Parameters: " + + PREFIX_CUSTOMER + "CUSTOMER_ID " + + PREFIX_PRODUCT + "PRODUCT_ID " + + PREFIX_QUANTITY + "QUANTITY " + + "[" + PREFIX_DATETIME + "DATETIME] " + + "[" + PREFIX_MONEY + "MONEY] " + + "[" + PREFIX_TRANS_DESCRIPTION + "DESCRIPTION] \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_CUSTOMER + "1 " + + PREFIX_PRODUCT + "1 " + + PREFIX_QUANTITY + "1 " + + PREFIX_DATETIME + DateTime.DEFAULT_VALUE.format(DateTime.DATE_TIME_FORMAT) + " " + + PREFIX_MONEY + "30 " + + PREFIX_TRANS_DESCRIPTION + "under discount "; + + public static final String MESSAGE_SUCCESS = "New transaction added: %1$s"; + + private final TransactionFactory transactionFactory; + + public AddTransactionCommand(TransactionFactory transactionFactory) { + requireNonNull(transactionFactory); + this.transactionFactory = transactionFactory; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredProductList(); + Index productIndex = getProductIndex(lastShownList); + + Product productToEdit = lastShownList.get(productIndex.getZeroBased()); + + Transaction toAdd = transactionFactory.createTransaction(model); + + if (model.hasTransaction(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_TRANSACTION); + } + + + Product editedProduct = updateProduct(productToEdit, toAdd); + + if (!productToEdit.isSameProduct(editedProduct) && model.hasProduct(editedProduct)) { + throw new CommandException(MESSAGE_DUPLICATE_PRODUCT); + } + + model.addTransaction(toAdd); + + model.setProduct(productToEdit, editedProduct); + model.updateFilteredProductList(); + + // display notification window if threshold is reached + if (editedProduct.reachesThreshold()) { + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), + null, + editedProduct, + "", + false, + true, + false, + false); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + /** + * Updates product details based on transaction to be added. + * @param productToEdit + * @param toAdd + * @throws CommandException + */ + private Product updateProduct(Product productToEdit, Transaction toAdd) throws CommandException { + EditProductDescriptor editProductDescriptor = new EditProductDescriptor(); + + editProductDescriptor.setQuantity(getNewQuantity(toAdd, productToEdit)); + editProductDescriptor.setSales(getNewSales(toAdd, productToEdit)); + editProductDescriptor.setThreshold(productToEdit.getThreshold()); + + Product editedProduct = createEditedProduct(productToEdit, editProductDescriptor); + + return editedProduct; + } + + private Index getProductIndex(List lastShownList) throws CommandException { + Index productIndex = transactionFactory.getProductIndex(); + if (productIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX); + } + return productIndex; + } + + private Quantity getNewQuantity(Transaction toAdd, Product productToEdit) throws CommandException { + Quantity oldQuantity = productToEdit.getQuantity(); + + if (oldQuantity.compareTo(toAdd.getQuantity()) == 0) { + throw new CommandException(Messages.MESSAGE_ZERO_PRODUCT_QUANTITY); + } else if (oldQuantity.compareTo(toAdd.getQuantity()) < 0) { + throw new CommandException(String.format(Messages.MESSAGE_INVALID_PRODUCT_QUANTITY, + oldQuantity.getValue(), productToEdit.getDescription().value)); + } + + return oldQuantity.minus(toAdd.getQuantity()); + } + + private Money getNewSales(Transaction toAdd, Product productToEdit) { + Money oldSales = productToEdit.getMoney(); + Money newSales; + + newSales = oldSales.plus(toAdd.getMoney()); + + return newSales; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTransactionCommand // instanceof handles nulls + && transactionFactory.equals(((AddTransactionCommand) other).transactionFactory)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/transaction/ClearTransactionCommand.java b/src/main/java/seedu/address/logic/commands/transaction/ClearTransactionCommand.java new file mode 100644 index 00000000000..7785744993d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/transaction/ClearTransactionCommand.java @@ -0,0 +1,27 @@ +package seedu.address.logic.commands.transaction; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.InventorySystem; +import seedu.address.model.Model; + +/** + * Clears the transaction list. + */ +public class ClearTransactionCommand extends Command { + + public static final String COMMAND_WORD = "cleart"; + + public static final String MESSAGE_SUCCESS = "Transaction list has been cleared!"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.setInventorySystem(new InventorySystem(), COMMAND_WORD); + return new CommandResult(MESSAGE_SUCCESS); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/transaction/EditTransactionCommand.java b/src/main/java/seedu/address/logic/commands/transaction/EditTransactionCommand.java new file mode 100644 index 00000000000..c89d7136f7f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/transaction/EditTransactionCommand.java @@ -0,0 +1,408 @@ +package seedu.address.logic.commands.transaction; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_PRODUCT; +import static seedu.address.logic.commands.product.EditProductCommand.EditProductDescriptor; +import static seedu.address.logic.commands.product.EditProductCommand.createEditedProduct; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MONEY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_QUANTITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TRANS_DESCRIPTION; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PRODUCTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TRANSACTIONS; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; + +/** + * Edits the details of an existing transaction in the address book. + */ +public class EditTransactionCommand extends Command { + + public static final String COMMAND_WORD = "editt"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the transaction identified " + + "by the index number used in the displayed transaction list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_CUSTOMER + "CUSTOMER] " + + "[" + PREFIX_PRODUCT + "PRODUCT] " + + "[" + PREFIX_DATETIME + "DATETIME] " + + "[" + PREFIX_QUANTITY + "QUANTITY] " + + "[" + PREFIX_MONEY + "MONEY] " + + "[" + PREFIX_TRANS_DESCRIPTION + "DESCRIPTION] \n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_CUSTOMER + "1 " + + PREFIX_PRODUCT + "1 " + + PREFIX_DATETIME + "2020-02-20 10:00 " + + PREFIX_QUANTITY + "3 " + + PREFIX_MONEY + "40 " + + PREFIX_TRANS_DESCRIPTION + "normal price "; + + public static final String MESSAGE_EDIT_TRANSACTION_SUCCESS = "Edited Transaction: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_TRANSACTION = + "This transaction already exists in the transaction list."; + + private final Index index; + private final EditTransactionDescriptor editTransactionDescriptor; + + /** + * @param index of the transaction in the filtered transaction list to edit + * @param editTransactionDescriptor details to edit the transaction with + */ + public EditTransactionCommand(Index index, EditTransactionDescriptor editTransactionDescriptor) { + requireNonNull(index); + requireNonNull(editTransactionDescriptor); + + this.index = index; + this.editTransactionDescriptor = new EditTransactionDescriptor(editTransactionDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + // create edited transaction + List lastShownTransactionList = model.getFilteredTransactionList(); + + if (index.getZeroBased() >= lastShownTransactionList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TRANSACTION_DISPLAYED_INDEX); + } + + Transaction transactionToEdit = lastShownTransactionList.get(index.getZeroBased()); + Transaction editedTransaction = createEditedTransaction(transactionToEdit, editTransactionDescriptor, model); + + if (modelHasDuplicateTransaction(model, editedTransaction, transactionToEdit)) { + throw new CommandException(MESSAGE_DUPLICATE_TRANSACTION); + } + + // update transaction list + model.setTransaction(transactionToEdit, editedTransaction); + model.updateFilteredTransactionList(PREDICATE_SHOW_ALL_TRANSACTIONS); + + // update product details + updateProduct(model, transactionToEdit, editedTransaction); + + return new CommandResult(String.format(MESSAGE_EDIT_TRANSACTION_SUCCESS, editedTransaction)); + } + + /** + * Update product quantity and money based on edited transaction. + * @param model + * @param transactionToEdit + * @param editedTransaction + * @throws CommandException + */ + private void updateProduct(Model model, Transaction transactionToEdit, Transaction editedTransaction) + throws CommandException { + EditProductDescriptor editUpdatedProductDescriptor = new EditProductDescriptor(); + + Product originalProductToEdit = model.findProductById(transactionToEdit.getProductId()); + Product editedOriginalProduct = createEditedOriginalProduct(originalProductToEdit, transactionToEdit); + + if (!originalProductToEdit.isSameProduct(editedOriginalProduct) && model.hasProduct(editedOriginalProduct)) { + throw new CommandException(MESSAGE_DUPLICATE_PRODUCT); + } + + model.setProduct(originalProductToEdit, editedOriginalProduct); + + Product updatedProductToEdit = model.findProductById(editedTransaction.getProductId()); + + Quantity updatedProductOldQuantity = updatedProductToEdit.getQuantity(); + Money updatedProductOldSales = updatedProductToEdit.getMoney(); + + if (updatedProductOldQuantity.compareTo(editedTransaction.getQuantity()) >= 0) { + Quantity updatedProductNewQuantity = updatedProductOldQuantity.minus(editedTransaction.getQuantity()); + Money updatedProductNewSales = updatedProductOldSales.plus(editedTransaction.getMoney()); + editUpdatedProductDescriptor.setQuantity(updatedProductNewQuantity); + editUpdatedProductDescriptor.setSales(updatedProductNewSales); + } else { + model.setProduct(editedOriginalProduct, originalProductToEdit); + throw new CommandException(String.format(Messages.MESSAGE_INVALID_PRODUCT_QUANTITY, + updatedProductOldQuantity.getValue(), updatedProductToEdit.getDescription().value)); + } + + Product editedUpdatedProduct = createEditedProduct(updatedProductToEdit, editUpdatedProductDescriptor); + + if (!updatedProductToEdit.isSameProduct(editedUpdatedProduct) && model.hasProduct(editedUpdatedProduct)) { + throw new CommandException(MESSAGE_DUPLICATE_PRODUCT); + } + + model.setProduct(updatedProductToEdit, editedUpdatedProduct); + model.updateFilteredProductList(PREDICATE_SHOW_ALL_PRODUCTS); + } + + /** + * Create the edited product from the original product in the transaction. + * @param originalProductToEdit + * @param transactionToEdit + * @return edited product + */ + private Product createEditedOriginalProduct(Product originalProductToEdit, + Transaction transactionToEdit) { + EditProductDescriptor editOriginalProductDescriptor = new EditProductDescriptor(); + + Quantity originalProductOldQuantity = originalProductToEdit.getQuantity(); + Quantity originalProductNewQuantity = originalProductOldQuantity.plus(transactionToEdit.getQuantity()); + editOriginalProductDescriptor.setQuantity(originalProductNewQuantity); + + Money originalProductOldSales = originalProductToEdit.getMoney(); + Money originalProductNewSales = originalProductOldSales.minus(transactionToEdit.getMoney()); + editOriginalProductDescriptor.setSales(originalProductNewSales); + + return createEditedProduct(originalProductToEdit, editOriginalProductDescriptor); + } + + /** + * Check whether model has duplicate product + * @param model + * @param editedTransaction + * @return true if model has duplicate product, else false + */ + private boolean modelHasDuplicateTransaction(Model model, + Transaction editedTransaction, + Transaction transactionToEdit) { + List transactions = model.getInventorySystem().getTransactionList(); + for (int i = 0; i < transactions.size(); i++) { + Transaction transaction = transactions.get(i); + if (!transaction.equals(transactionToEdit) && transaction.equals(editedTransaction)) { + return true; + } + } + return false; + } + + /** + * Creates and returns a {@code Transaction} with the details of {@code transactionToEdit} + * edited with {@code editTransactionDescriptor}. + */ + private static Transaction createEditedTransaction(Transaction transactionToEdit, + EditTransactionDescriptor editTransactionDescriptor, + Model model) throws CommandException { + assert transactionToEdit != null; + + Customer updatedCustomer = getCustomer(editTransactionDescriptor, model, transactionToEdit); + UUID updatedCustomerId = updatedCustomer.getId(); + + Product updatedProduct = getProduct(editTransactionDescriptor, model, transactionToEdit); + UUID updatedProductId = updatedProduct.getId(); + + DateTime updatedDateTime = editTransactionDescriptor.getDateTime().orElse(transactionToEdit.getDateTime()); + Quantity updatedQuantity = editTransactionDescriptor.getQuantity().orElse(transactionToEdit.getQuantity()); + Money updatedMoney = editTransactionDescriptor.getMoney().orElse(transactionToEdit.getMoney()); + Description updatedDescription = editTransactionDescriptor.getDescription() + .orElse(transactionToEdit.getDescription()); + + return new Transaction(updatedCustomer, updatedProduct, updatedCustomerId, updatedProductId, + updatedDateTime, updatedQuantity, updatedMoney, updatedDescription); + } + + /** + * Returns associated customer for edited transaction. + * @param editTransactionDescriptor + * @param model + * @param transactionToEdit + * @return associated customer + * @throws CommandException + */ + private static Customer getCustomer(EditTransactionDescriptor editTransactionDescriptor, + Model model, + Transaction transactionToEdit) throws CommandException { + List lastShownCustomerList = model.getFilteredCustomerList(); + + if (editTransactionDescriptor.getCustomerIndex().isPresent()) { + Index updatedCustomerIndex = editTransactionDescriptor.getCustomerIndex().get(); + + if (updatedCustomerIndex.getZeroBased() >= lastShownCustomerList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + return model.getFilteredCustomerList().get(updatedCustomerIndex.getZeroBased()); + } else { + return transactionToEdit.getCustomer(); + } + } + + /** + * Returns associated product for edited transaction. + * @param editTransactionDescriptor + * @param model + * @param transactionToEdit + * @return associated product + * @throws CommandException + */ + private static Product getProduct(EditTransactionDescriptor editTransactionDescriptor, + Model model, + Transaction transactionToEdit) throws CommandException { + List lastShownProductList = model.getFilteredProductList(); + + if (editTransactionDescriptor.getProductIndex().isPresent()) { + Index updatedProductIndex = editTransactionDescriptor.getProductIndex().get(); + + if (updatedProductIndex.getZeroBased() >= lastShownProductList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX); + } + + return model.getFilteredProductList().get(updatedProductIndex.getZeroBased()); + } else { + return transactionToEdit.getProduct(); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditTransactionCommand)) { + return false; + } + + // state check + EditTransactionCommand e = (EditTransactionCommand) other; + return index.equals(e.index) + && editTransactionDescriptor.equals(e.editTransactionDescriptor); + } + + /** + * Stores the details to edit the transaction with. Each non-empty field value will replace the + * corresponding field value of the transaction. + */ + public static class EditTransactionDescriptor { + private Index customerIndex; + private Index productIndex; + private DateTime dateTime; + private Quantity quantity; + private Money money; + private Description description; + + public EditTransactionDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditTransactionDescriptor(EditTransactionDescriptor toCopy) { + setCustomerIndex(toCopy.customerIndex); + setProductIndex(toCopy.productIndex); + setDateTime(toCopy.dateTime); + setQuantity(toCopy.quantity); + setMoney(toCopy.money); + setDescription(toCopy.description); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(customerIndex, productIndex, dateTime, quantity, + money, description); + } + + public void setCustomerIndex(Index customerIndex) { + this.customerIndex = customerIndex; + } + + public void setProductIndex(Index productIndex) { + this.productIndex = productIndex; + } + + public void setDateTime(DateTime dateTime) { + this.dateTime = dateTime; + } + + public void setQuantity(Quantity quantity) { + this.quantity = quantity; + } + + public void setMoney(Money money) { + this.money = money; + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getCustomerIndex() { + return Optional.ofNullable(customerIndex); + } + + public Optional getProductIndex() { + return Optional.ofNullable(productIndex); + } + + public Optional getDateTime() { + return Optional.ofNullable(dateTime); + } + + public Optional getQuantity() { + return Optional.ofNullable(quantity); + } + + public Optional getMoney() { + return Optional.ofNullable(money); + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditTransactionDescriptor)) { + return false; + } + + // state check + EditTransactionDescriptor e = (EditTransactionDescriptor) other; + + return getCustomerIndex().equals(e.getCustomerIndex()) + && getProductIndex().equals(e.getProductIndex()) + && getDateTime().equals(e.getDateTime()) + && getQuantity().equals(e.getQuantity()) + && getMoney().equals(e.getMoney()) + && getDescription().equals(e.getDescription()); + } + + @Override + public String toString() { + return "EditTransactionDescriptor{" + + "customerIndex=" + customerIndex + + ", productIndex=" + productIndex + + ", dateTime=" + dateTime + + ", quantity=" + quantity + + ", money=" + money + + ", description=" + description + + '}'; + } + } +} + diff --git a/src/main/java/seedu/address/logic/commands/transaction/FindTransactionCommand.java b/src/main/java/seedu/address/logic/commands/transaction/FindTransactionCommand.java new file mode 100644 index 00000000000..c554cb62f5f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/transaction/FindTransactionCommand.java @@ -0,0 +1,60 @@ +package seedu.address.logic.commands.transaction; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MONEY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCT; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.transaction.Transaction; + +/** + * Finds and lists all transactions in transaction list whose description contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindTransactionCommand extends Command { + + public static final String COMMAND_WORD = "findt"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all products whose descriptions contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers. " + + "At least one field must be present in the command. \n" + + "Parameters: " + + "[" + PREFIX_CUSTOMER + "CUSTOMER_NAME [CUSTOMER_NAME]...] " + + "[" + PREFIX_PRODUCT + "PRODUCT_NAME [PRODUCT_NAME]...] " + + "[" + PREFIX_MONEY + "MONEY] " + + "[" + PREFIX_DATETIME + "DATETIME] \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_CUSTOMER + "Bob " + + PREFIX_PRODUCT + "WaterMelon " + + PREFIX_MONEY + "30 " + + PREFIX_DATETIME + "2020-02-20 10:00 "; + + private final Predicate predicate; + + public FindTransactionCommand(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTransactionList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_TRANSACTIONS_LISTED_OVERVIEW, + model.getFilteredTransactionList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindTransactionCommand // instanceof handles nulls + && predicate.equals(((FindTransactionCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/transaction/ListTransactionCommand.java b/src/main/java/seedu/address/logic/commands/transaction/ListTransactionCommand.java new file mode 100644 index 00000000000..fb4b90d9b8b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/transaction/ListTransactionCommand.java @@ -0,0 +1,27 @@ +package seedu.address.logic.commands.transaction; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TRANSACTIONS; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all transactions in the application to the user. + */ +public class ListTransactionCommand extends Command { + + public static final String COMMAND_WORD = "listt"; + + public static final String MESSAGE_SUCCESS = "Listed all transactions"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTransactionList(PREDICATE_SHOW_ALL_TRANSACTIONS); + return new CommandResult(MESSAGE_SUCCESS); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/transaction/UndoTransactionCommand.java b/src/main/java/seedu/address/logic/commands/transaction/UndoTransactionCommand.java new file mode 100644 index 00000000000..971c42300f9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/transaction/UndoTransactionCommand.java @@ -0,0 +1,93 @@ +package seedu.address.logic.commands.transaction; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_PRODUCT; +import static seedu.address.logic.commands.product.EditProductCommand.EditProductDescriptor; +import static seedu.address.logic.commands.product.EditProductCommand.createEditedProduct; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; + +/** + * Undo a transaction identified using it's displayed index from the transaction list. + */ +public class UndoTransactionCommand extends Command { + + public static final String COMMAND_WORD = "undot"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Undo the transaction identified by the index number used in the displayed transaction list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_UNDO_PERSON_SUCCESS = "Undo Transaction: %1$s"; + + private final Index targetIndex; + + public UndoTransactionCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + // delete transaction + List lastShownList = model.getFilteredTransactionList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TRANSACTION_DISPLAYED_INDEX); + } + + Transaction transactionToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteTransaction(transactionToDelete); + + // update product quantity and money + updateProduct(model, transactionToDelete); + return new CommandResult(String.format(MESSAGE_UNDO_PERSON_SUCCESS, transactionToDelete)); + } + + /** + * Adds the quantity and money back to the product of the transaction to be undo. + * @param model + * @param transactionToUndo + * @throws CommandException + */ + private void updateProduct(Model model, Transaction transactionToUndo) throws CommandException { + EditProductDescriptor editProductDescriptor = new EditProductDescriptor(); + Product productToEdit = model.findProductById(transactionToUndo.getProductId()); + + Quantity oldQuantity = productToEdit.getQuantity(); + Quantity newQuantity = oldQuantity.plus(transactionToUndo.getQuantity()); + editProductDescriptor.setQuantity(newQuantity); + + Money oldSales = productToEdit.getMoney(); + Money newSales = oldSales.minus(transactionToUndo.getMoney()); + editProductDescriptor.setSales(newSales); + + Product editedProduct = createEditedProduct(productToEdit, editProductDescriptor); + if (!productToEdit.isSameProduct(editedProduct) && model.hasProduct(editedProduct)) { + throw new CommandException(MESSAGE_DUPLICATE_PRODUCT); + } + + model.setProduct(productToEdit, editedProduct); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UndoTransactionCommand // instanceof handles nulls + && targetIndex.equals(((UndoTransactionCommand) other).targetIndex)); // state check + } +} + diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e8..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 1e466792b46..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java index 954c8e18f8e..c6055ba3d08 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -6,6 +6,8 @@ import java.util.Map; import java.util.Optional; +import seedu.address.logic.parser.exceptions.ParseException; + /** * Stores mapping of prefixes to their respective arguments. * Each key may be associated with multiple argument values. @@ -39,6 +41,14 @@ public Optional getValue(Prefix prefix) { return values.isEmpty() ? Optional.empty() : Optional.of(values.get(values.size() - 1)); } + /** + * Returns true if a prefix occurs more than one time. + */ + public boolean hasDuplicateValues(Prefix prefix) { + List values = getAllValues(prefix); + return values.size() > 1; + } + /** * Returns all values of {@code prefix}. * If the prefix does not exist or has no values, this will return an empty list. @@ -54,7 +64,7 @@ public List getAllValues(Prefix prefix) { /** * Returns the preamble (text before the first valid prefix). Trims any leading/trailing spaces. */ - public String getPreamble() { + public String getPreamble() throws ParseException { return getValue(new Prefix("")).orElse(""); } } diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..0394c3d360d 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -5,11 +5,29 @@ */ public class CliSyntax { - /* Prefix definitions */ + /* Prefix definitions for customer */ public static final Prefix PREFIX_NAME = new Prefix("n/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + /* Prefix definitions for product */ + public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/"); + public static final Prefix PREFIX_COSTPRICE = new Prefix("cp/"); + public static final Prefix PREFIX_PRICE = new Prefix("pr/"); + public static final Prefix PREFIX_QUANTITY = new Prefix("q/"); + public static final Prefix PREFIX_SALES = new Prefix("s/"); + public static final Prefix PREFIX_THRESHOLD = new Prefix("t/"); + + /* Prefix definitions for transactions */ + public static final Prefix PREFIX_CUSTOMER = new Prefix("c/"); + public static final Prefix PREFIX_PRODUCT = new Prefix("p/"); + public static final Prefix PREFIX_DATETIME = new Prefix("dt/"); + public static final Prefix PREFIX_MONEY = new Prefix("m/"); + public static final Prefix PREFIX_TRANS_DESCRIPTION = new Prefix("d/"); + + /* Prefix definitions for statistics */ + public static final Prefix PREFIX_START_DATE = new Prefix("sd/"); + public static final Prefix PREFIX_END_DATE = new Prefix("ed/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 522b93081cc..00000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/InventorySystemParser.java b/src/main/java/seedu/address/logic/parser/InventorySystemParser.java new file mode 100644 index 00000000000..7645faf4786 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/InventorySystemParser.java @@ -0,0 +1,162 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.HelpCommand; + +import seedu.address.logic.commands.customer.AddCustomerCommand; +import seedu.address.logic.commands.customer.ClearCustomerCommand; +import seedu.address.logic.commands.customer.DeleteCustomerCommand; +import seedu.address.logic.commands.customer.EditCustomerCommand; +import seedu.address.logic.commands.customer.FindCustomerCommand; +import seedu.address.logic.commands.customer.ListCustomerCommand; +import seedu.address.logic.commands.product.AddProductCommand; +import seedu.address.logic.commands.product.ClearProductCommand; +import seedu.address.logic.commands.product.DeleteProductCommand; +import seedu.address.logic.commands.product.EditProductCommand; +import seedu.address.logic.commands.product.FindProductCommand; +import seedu.address.logic.commands.product.ListProductCommand; +import seedu.address.logic.commands.product.LowLimitCommand; +import seedu.address.logic.commands.product.PlotSalesCommand; +import seedu.address.logic.commands.statistics.PredictCommand; +import seedu.address.logic.commands.statistics.ProfitCommand; +import seedu.address.logic.commands.statistics.RevenueCommand; +import seedu.address.logic.commands.transaction.AddTransactionCommand; +import seedu.address.logic.commands.transaction.ClearTransactionCommand; +import seedu.address.logic.commands.transaction.EditTransactionCommand; +import seedu.address.logic.commands.transaction.FindTransactionCommand; +import seedu.address.logic.commands.transaction.ListTransactionCommand; +import seedu.address.logic.commands.transaction.UndoTransactionCommand; + +import seedu.address.logic.parser.customer.AddCustomerCommandParser; +import seedu.address.logic.parser.customer.DeleteCustomerCommandParser; +import seedu.address.logic.parser.customer.EditCustomerCommandParser; +import seedu.address.logic.parser.customer.FindCustomerCommandParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.product.AddProductCommandParser; +import seedu.address.logic.parser.product.DeleteProductCommandParser; +import seedu.address.logic.parser.product.EditProductCommandParser; +import seedu.address.logic.parser.product.FindProductCommandParser; +import seedu.address.logic.parser.product.LowLimitCommandParser; +import seedu.address.logic.parser.product.PlotSalesCommandParser; +import seedu.address.logic.parser.statistics.ProfitCommandParser; +import seedu.address.logic.parser.statistics.RevenueCommandParser; +import seedu.address.logic.parser.transaction.AddTransactionCommandParser; +import seedu.address.logic.parser.transaction.EditTransactionCommandParser; +import seedu.address.logic.parser.transaction.FindTransactionCommandParser; +import seedu.address.logic.parser.transaction.UndoTransactionCommandParser; + +/** + * Parses user input. + */ +public class InventorySystemParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case AddCustomerCommand.COMMAND_WORD: + return new AddCustomerCommandParser().parse(arguments); + + case EditCustomerCommand.COMMAND_WORD: + return new EditCustomerCommandParser().parse(arguments); + + case DeleteCustomerCommand.COMMAND_WORD: + return new DeleteCustomerCommandParser().parse(arguments); + + case ClearCustomerCommand.COMMAND_WORD: + return new ClearCustomerCommand(); + + case FindCustomerCommand.COMMAND_WORD: + return new FindCustomerCommandParser().parse(arguments); + + case ListCustomerCommand.COMMAND_WORD: + return new ListCustomerCommand(); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case AddProductCommand.COMMAND_WORD: + return new AddProductCommandParser().parse(arguments); + + case ListProductCommand.COMMAND_WORD: + return new ListProductCommand(); + + case ClearProductCommand.COMMAND_WORD: + return new ClearProductCommand(); + + case DeleteProductCommand.COMMAND_WORD: + return new DeleteProductCommandParser().parse(arguments); + + case EditProductCommand.COMMAND_WORD: + return new EditProductCommandParser().parse(arguments); + + case FindProductCommand.COMMAND_WORD: + return new FindProductCommandParser().parse(arguments); + + case LowLimitCommand.COMMAND_WORD: + return new LowLimitCommandParser().parse(arguments); + + case AddTransactionCommand.COMMAND_WORD: + return new AddTransactionCommandParser().parse(arguments); + + case EditTransactionCommand.COMMAND_WORD: + return new EditTransactionCommandParser().parse(arguments); + + case FindTransactionCommand.COMMAND_WORD: + return new FindTransactionCommandParser().parse(arguments); + + case ListTransactionCommand.COMMAND_WORD: + return new ListTransactionCommand(); + + case UndoTransactionCommand.COMMAND_WORD: + return new UndoTransactionCommandParser().parse(arguments); + + case ClearTransactionCommand.COMMAND_WORD: + return new ClearTransactionCommand(); + + case RevenueCommand.COMMAND_WORD: + return new RevenueCommandParser().parse(arguments); + + case ProfitCommand.COMMAND_WORD: + return new ProfitCommandParser().parse(arguments); + + case PredictCommand.COMMAND_WORD: + return new PredictCommand(); + + case PlotSalesCommand.COMMAND_WORD: + return new PlotSalesCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..cc6efcb4b0b 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -9,11 +9,19 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.address.model.customer.Address; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; +import seedu.address.model.product.CostPrice; +import seedu.address.model.product.Price; +import seedu.address.model.product.ProductQuantity; import seedu.address.model.tag.Tag; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.TransactionQuantity; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.QuantityThreshold; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -121,4 +129,155 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String address} into an {@code Address}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code address} is invalid. + */ + public static Description parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!Description.isValidDescription(trimmedDescription)) { + throw new ParseException(Description.MESSAGE_CONSTRAINTS); + } + return new Description(trimmedDescription); + } + + /** + * Parses a {@code String address} into an {@code Address}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code address} is invalid. + */ + public static Price parsePrice(String price) throws ParseException { + requireNonNull(price); + String trimmedPrice = price.trim(); + if (!Price.isValidPrice(trimmedPrice)) { + throw new ParseException(Price.MESSAGE_CONSTRAINTS); + } + return new Price(trimmedPrice); + } + + /** + * Parses a {@code String address} into an {@code Address}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code address} is invalid. + */ + public static CostPrice parseCostPrice(String price) throws ParseException { + requireNonNull(price); + String trimmedPrice = price.trim(); + if (!CostPrice.isValidPrice(trimmedPrice)) { + throw new ParseException(Price.MESSAGE_CONSTRAINTS); + } + return new CostPrice(trimmedPrice); + } + + /** + * Parses a {@code String address} into an {@code Address}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code address} is invalid. + */ + public static TransactionQuantity parseTransactionQuantity(String quantity) throws ParseException { + requireNonNull(quantity); + String trimmedQuantity = quantity.trim(); + if (!TransactionQuantity.isValidFormat(trimmedQuantity)) { + throw new ParseException(TransactionQuantity.MESSAGE_CONSTRAINTS_FORMAT); + } + + if (!TransactionQuantity.isValidValue(Integer.parseInt(trimmedQuantity))) { + throw new ParseException(TransactionQuantity.MESSAGE_CONSTRAINTS_VALUE); + } + return new TransactionQuantity(trimmedQuantity); + } + + /** + * Parses a {@code String address} into an {@code Address}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code address} is invalid. + */ + public static ProductQuantity parseProductQuantity(String quantity) throws ParseException { + requireNonNull(quantity); + String trimmedQuantity = quantity.trim(); + if (!ProductQuantity.isValidFormat(trimmedQuantity)) { + throw new ParseException(ProductQuantity.MESSAGE_CONSTRAINTS_FORMAT); + } + + if (!ProductQuantity.isValidValue(Integer.parseInt(trimmedQuantity))) { + throw new ParseException(ProductQuantity.MESSAGE_CONSTRAINTS_VALUE); + } + return new ProductQuantity(trimmedQuantity); + } + + /** + * Parses a {@code String product} into an {@code product}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code product} is invalid. + */ + public static DateTime parseDateTime(String dateTime) throws ParseException { + requireNonNull(dateTime); + String trimmedDateTime = dateTime.trim(); + if (!DateTime.isValidDateTime(trimmedDateTime)) { + throw new ParseException(DateTime.MESSAGE_CONSTRAINTS); + } + return new DateTime(trimmedDateTime); + } + + /** + * Parses a {@code String money} into an {@code Money}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code money} is invalid. + */ + public static Money parseMoney(String money) throws ParseException { + requireNonNull(money); + String trimmedMoney = money.trim(); + + if (!Money.isValidMoney(trimmedMoney)) { + throw new ParseException(Money.MESSAGE_CONSTRAINTS_FORMAT); + } + + if (!Money.isValidAmount(Integer.parseInt(trimmedMoney))) { + throw new ParseException(Money.MESSAGE_CONSTRAINTS_VALUE); + } + + return new Money(trimmedMoney); + } + + + + /** + * Parses a {@code String address} into an {@code Address}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code address} is invalid. + */ + public static Description parseTransDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!Description.isValidDescription(trimmedDescription)) { + throw new ParseException(Description.MESSAGE_CONSTRAINTS); + } + return new Description(trimmedDescription); + } + + /** + * Parses a {@code String threshold} into an {@code trimmedThreshold}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). + */ + public static QuantityThreshold parseThreshold(String threshold) throws ParseException { + requireNonNull(threshold); + String trimmedThreshold = threshold.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedThreshold)) { + throw new ParseException(QuantityThreshold.MESSAGE_CONSTRAINTS); + } + return new QuantityThreshold(trimmedThreshold); + } } diff --git a/src/main/java/seedu/address/logic/parser/customer/AddCustomerCommandParser.java b/src/main/java/seedu/address/logic/parser/customer/AddCustomerCommandParser.java new file mode 100644 index 00000000000..36c539fe688 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/customer/AddCustomerCommandParser.java @@ -0,0 +1,94 @@ +package seedu.address.logic.parser.customer; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.commands.customer.AddCustomerCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.customer.Address; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new AddCustomerCommand object + */ +public class AddCustomerCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCustomerCommand + * and returns an AddCustomerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddCustomerCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCustomerCommand.MESSAGE_USAGE)); + } + + if (anyPrefixesDuplicate(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + AddCustomerCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Email email = getEmail(argMultimap); + Address address = getAddress(argMultimap); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + Customer customer = new Customer(name, phone, email, address, tagList); + + return new AddCustomerCommand(customer); + } + + private Address getAddress(ArgumentMultimap argMultimap) throws ParseException { + if (arePrefixesPresent(argMultimap, PREFIX_ADDRESS)) { + return ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + } else { + return new Address(Address.DEFAULT_VALUE); + } + } + + private Email getEmail(ArgumentMultimap argMultimap) throws ParseException { + if (arePrefixesPresent(argMultimap, PREFIX_EMAIL)) { + return ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + } else { + return new Email(Email.DEFAULT_VALUE); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/customer/DeleteCustomerCommandParser.java b/src/main/java/seedu/address/logic/parser/customer/DeleteCustomerCommandParser.java new file mode 100644 index 00000000000..5c738ccbef5 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/customer/DeleteCustomerCommandParser.java @@ -0,0 +1,34 @@ +package seedu.address.logic.parser.customer; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.customer.DeleteCustomerCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteCustomerCommand object + */ +public class DeleteCustomerCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCustomerCommand + * and returns a DeleteCustomerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteCustomerCommand parse(String args) throws ParseException { + Index index; + + try { + index = ParserUtil.parseIndex(args); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCustomerCommand.MESSAGE_USAGE), pe); + } + + return new DeleteCustomerCommand(index); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/customer/EditCustomerCommandParser.java b/src/main/java/seedu/address/logic/parser/customer/EditCustomerCommandParser.java new file mode 100644 index 00000000000..b726e8bad39 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/customer/EditCustomerCommandParser.java @@ -0,0 +1,127 @@ +package seedu.address.logic.parser.customer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.customer.EditCustomerCommand; +import seedu.address.logic.commands.customer.EditCustomerCommand.EditCustomerDescriptor; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new EditCustomerCommand object. + */ +public class EditCustomerCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditCustomerCommand + * and returns an EditCustomerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditCustomerCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + + if (!anyPrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG) + || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditCustomerCommand.MESSAGE_USAGE)); + } + + if (anyPrefixesDuplicate(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + EditCustomerCommand.MESSAGE_USAGE)); + } + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, EditCustomerCommand.MESSAGE_USAGE), pe); + } + + EditCustomerDescriptor editCustomerDescriptor = getEditPersonDescriptor(argMultimap); + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editCustomerDescriptor::setTags); + + if (!editCustomerDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditCustomerCommand.MESSAGE_NOT_EDITED); + } + + return new EditCustomerCommand(index, editCustomerDescriptor); + } + + /** + * Get an edit person descriptor from the user input values. + * @param argMultimap + * @return edit person descriptor + * @throws ParseException + */ + private EditCustomerDescriptor getEditPersonDescriptor(ArgumentMultimap argMultimap) throws ParseException { + EditCustomerCommand.EditCustomerDescriptor editCustomerDescriptor = new EditCustomerDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editCustomerDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editCustomerDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + editCustomerDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + editCustomerDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + } + return editCustomerDescriptor; + } + + /** + * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. + * If {@code tags} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero tags. + */ + private Optional> parseTagsForEdit(Collection tags) throws ParseException { + assert tags != null; + + if (tags.isEmpty()) { + return Optional.empty(); + } + Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; + return Optional.of(ParserUtil.parseTags(tagSet)); + } + + /** + * Returns true if any of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/customer/FindCustomerCommandParser.java b/src/main/java/seedu/address/logic/parser/customer/FindCustomerCommandParser.java new file mode 100644 index 00000000000..0c19b2b7f0b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/customer/FindCustomerCommandParser.java @@ -0,0 +1,103 @@ +package seedu.address.logic.parser.customer; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import seedu.address.logic.commands.customer.FindCustomerCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.customer.AddressContainsKeywordsPredicate; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.EmailContainsKeywordsPredicate; +import seedu.address.model.customer.JointCustomerPredicate; +import seedu.address.model.customer.NameContainsKeywordsPredicate; +import seedu.address.model.customer.PhoneContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindCustomerCommand object + */ +public class FindCustomerCommandParser implements Parser { + private final List> predicates = new ArrayList<>(); + /** + * Parses the given {@code String} of arguments in the context of the FindCustomerCommand + * and returns a FindCustomerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindCustomerCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + + if (!anyPrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindCustomerCommand.MESSAGE_USAGE)); + } + + if (anyPrefixesDuplicate(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + FindCustomerCommand.MESSAGE_USAGE)); + } + + addToPredicates(argMultimap); + + return new FindCustomerCommand(new JointCustomerPredicate(predicates)); + } + + /** + * Add attributes entered by user to predicates list. + * @param argMultimap + * @throws ParseException + */ + private void addToPredicates(ArgumentMultimap argMultimap) throws ParseException { + if (anyPrefixesPresent(argMultimap, PREFIX_NAME)) { + String customerArgs = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()).toString(); + String[] customerKeywords = customerArgs.split("\\s+"); + predicates.add(new NameContainsKeywordsPredicate(Arrays.asList(customerKeywords))); + } + if (anyPrefixesPresent(argMultimap, PREFIX_ADDRESS)) { + String addressArgs = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()).toString(); + String[] addressKeywords = addressArgs.split("\\s+"); + predicates.add(new AddressContainsKeywordsPredicate(Arrays.asList(addressKeywords))); + } + if (anyPrefixesPresent(argMultimap, PREFIX_EMAIL)) { + String emailArgs = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()).toString(); + String[] emailKeywords = emailArgs.split("\\s+"); + predicates.add(new EmailContainsKeywordsPredicate(Arrays.asList(emailKeywords))); + } + if (anyPrefixesPresent(argMultimap, PREFIX_PHONE)) { + String phoneArgs = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()).toString(); + String[] phoneKeywords = phoneArgs.split("\\s+"); + predicates.add(new PhoneContainsKeywordsPredicate(Arrays.asList(phoneKeywords))); + } + } + + /** + * Returns true if any of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/product/AddProductCommandParser.java b/src/main/java/seedu/address/logic/parser/product/AddProductCommandParser.java new file mode 100644 index 00000000000..86308728c07 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/product/AddProductCommandParser.java @@ -0,0 +1,107 @@ +package seedu.address.logic.parser.product; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_INITIAL_QUANTITY; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COSTPRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_QUANTITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_THRESHOLD; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.product.AddProductCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.product.CostPrice; +import seedu.address.model.product.Price; +import seedu.address.model.product.Product; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; +import seedu.address.model.util.QuantityThreshold; + +/** + * Parses input arguments and creates a new AddProductCommand object + */ +public class AddProductCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddProductCommand + * and returns an AddProductCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddProductCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DESCRIPTION, PREFIX_COSTPRICE, + PREFIX_PRICE, PREFIX_QUANTITY, PREFIX_SALES); + + if (!arePrefixesPresent(argMultimap, PREFIX_DESCRIPTION, PREFIX_COSTPRICE, PREFIX_PRICE, PREFIX_QUANTITY) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddProductCommand.MESSAGE_USAGE)); + } + + if (anyPrefixesDuplicate(argMultimap, PREFIX_DESCRIPTION, PREFIX_COSTPRICE, + PREFIX_PRICE, PREFIX_QUANTITY, PREFIX_SALES)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + AddProductCommand.MESSAGE_USAGE)); + } + + Description description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + CostPrice costPrice = ParserUtil.parseCostPrice(argMultimap.getValue(PREFIX_COSTPRICE).get()); + Price price = ParserUtil.parsePrice(argMultimap.getValue(PREFIX_PRICE).get()); + Quantity quantity = ParserUtil.parseProductQuantity(argMultimap.getValue(PREFIX_QUANTITY).get()); + Money sales = getSales(argMultimap); + QuantityThreshold threshold = getThreshold(argMultimap, quantity); + + if (quantity.getValue() == 0) { + throw new ParseException(MESSAGE_INVALID_INITIAL_QUANTITY); + } + + Product product = new Product(description, costPrice, price, quantity, sales, threshold, 1); + + return new AddProductCommand(product); + } + + private Money getSales(ArgumentMultimap argMultimap) throws ParseException { + if (arePrefixesPresent(argMultimap, PREFIX_SALES)) { + return ParserUtil.parseMoney(argMultimap.getValue(PREFIX_SALES).get()); + } else { + return new Money(Money.DEFAULT_VALUE); + } + } + + private QuantityThreshold getThreshold(ArgumentMultimap argMultimap, Quantity quantity) + throws ParseException { + if (arePrefixesPresent(argMultimap, PREFIX_THRESHOLD)) { + return ParserUtil.parseThreshold(argMultimap.getValue(PREFIX_THRESHOLD).get()); + } else { + // sets the default threshold at 20% of initial quantity + String lowLimit = String.valueOf(quantity.getValue() / 5); + return new QuantityThreshold(lowLimit); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } +} + diff --git a/src/main/java/seedu/address/logic/parser/product/DeleteProductCommandParser.java b/src/main/java/seedu/address/logic/parser/product/DeleteProductCommandParser.java new file mode 100644 index 00000000000..e86f3f247ec --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/product/DeleteProductCommandParser.java @@ -0,0 +1,34 @@ +package seedu.address.logic.parser.product; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.product.DeleteProductCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteProductCommand object + */ +public class DeleteProductCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteProductCommand + * and returns a DeleteProductCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteProductCommand parse(String args) throws ParseException { + Index index; + + try { + index = ParserUtil.parseIndex(args); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteProductCommand.MESSAGE_USAGE), pe); + } + + return new DeleteProductCommand(index); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/product/EditProductCommandParser.java b/src/main/java/seedu/address/logic/parser/product/EditProductCommandParser.java new file mode 100644 index 00000000000..29c1d651ab0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/product/EditProductCommandParser.java @@ -0,0 +1,112 @@ +package seedu.address.logic.parser.product; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COSTPRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_QUANTITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALES; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.product.EditProductCommand; +import seedu.address.logic.commands.product.EditProductCommand.EditProductDescriptor; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditCommand object + */ +public class EditProductCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditProductCommand + * and returns an EditProductCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditProductCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DESCRIPTION, PREFIX_COSTPRICE, PREFIX_PRICE, + PREFIX_QUANTITY, PREFIX_SALES); + + if (!anyPrefixesPresent(argMultimap, PREFIX_DESCRIPTION, PREFIX_COSTPRICE, PREFIX_PRICE, + PREFIX_QUANTITY, PREFIX_SALES) + || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditProductCommand.MESSAGE_USAGE)); + } + + if (anyPrefixesDuplicate(argMultimap, PREFIX_DESCRIPTION, PREFIX_COSTPRICE, PREFIX_PRICE, + PREFIX_QUANTITY, PREFIX_SALES)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + EditProductCommand.MESSAGE_USAGE)); + } + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditProductCommand.MESSAGE_USAGE), pe); + } + + EditProductDescriptor editProductDescriptor = getEditProductDescriptor(argMultimap); + + return new EditProductCommand(index, editProductDescriptor); + } + + /** + * Get an edit product descriptor from the user input values. + * @param argMultimap + * @return edit product descriptor + * @throws ParseException + */ + private EditProductDescriptor getEditProductDescriptor(ArgumentMultimap argMultimap) throws ParseException { + EditProductDescriptor editProductDescriptor = new EditProductDescriptor(); + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editProductDescriptor.setDescription(ParserUtil + .parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get())); + } + if (argMultimap.getValue(PREFIX_COSTPRICE).isPresent()) { + editProductDescriptor.setCostPrice(ParserUtil + .parseCostPrice(argMultimap.getValue(PREFIX_COSTPRICE).get())); + } + if (argMultimap.getValue(PREFIX_PRICE).isPresent()) { + editProductDescriptor.setPrice(ParserUtil.parsePrice(argMultimap.getValue(PREFIX_PRICE).get())); + } + if (argMultimap.getValue(PREFIX_QUANTITY).isPresent()) { + editProductDescriptor.setQuantity( + ParserUtil.parseProductQuantity(argMultimap.getValue(PREFIX_QUANTITY).get()) + ); + } + if (argMultimap.getValue(PREFIX_SALES).isPresent()) { + editProductDescriptor.setSales(ParserUtil.parseMoney(argMultimap.getValue(PREFIX_SALES).get())); + } + return editProductDescriptor; + } + + /** + * Returns true if any of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/product/FindProductCommandParser.java similarity index 52% rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java rename to src/main/java/seedu/address/logic/parser/product/FindProductCommandParser.java index 4fb71f23103..6db3a59f290 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/product/FindProductCommandParser.java @@ -1,33 +1,35 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.product; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import java.util.Arrays; -import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.product.FindProductCommand; +import seedu.address.logic.parser.Parser; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.product.DescriptionContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object */ -public class FindCommandParser implements Parser { +public class FindProductCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. + * Parses the given {@code String} of arguments in the context of the FindProductCommand + * and returns a FindProductCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ - public FindCommand parse(String args) throws ParseException { + public FindProductCommand parse(String args) throws ParseException { String trimmedArgs = args.trim(); if (trimmedArgs.isEmpty()) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindProductCommand.MESSAGE_USAGE)); } String[] nameKeywords = trimmedArgs.split("\\s+"); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + return new FindProductCommand(new DescriptionContainsKeywordsPredicate(Arrays.asList(nameKeywords))); } } + diff --git a/src/main/java/seedu/address/logic/parser/product/LowLimitCommandParser.java b/src/main/java/seedu/address/logic/parser/product/LowLimitCommandParser.java new file mode 100644 index 00000000000..d809c7d2fc0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/product/LowLimitCommandParser.java @@ -0,0 +1,65 @@ +package seedu.address.logic.parser.product; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_THRESHOLD; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.product.LowLimitCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.util.QuantityThreshold; + +/** + * Parses input arguments and creates a new LowLimitCommand object. + */ +public class LowLimitCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the LowLimitCommand + * and returns a LowLimitCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public LowLimitCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_PRODUCT, PREFIX_THRESHOLD); + + if (!arePrefixesPresent(argMultimap, PREFIX_PRODUCT, PREFIX_THRESHOLD) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LowLimitCommand.MESSAGE_USAGE)); + } + + if (anyPrefixesDuplicate(argMultimap, PREFIX_PRODUCT, PREFIX_THRESHOLD)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + LowLimitCommand.MESSAGE_USAGE)); + } + + Index productIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_PRODUCT).get()); + QuantityThreshold threshold = ParserUtil.parseThreshold(argMultimap.getValue(PREFIX_THRESHOLD).get()); + + return new LowLimitCommand(productIndex, threshold); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/product/PlotSalesCommandParser.java b/src/main/java/seedu/address/logic/parser/product/PlotSalesCommandParser.java new file mode 100644 index 00000000000..cd8f0e3dbe3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/product/PlotSalesCommandParser.java @@ -0,0 +1,85 @@ +package seedu.address.logic.parser.product; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.product.PlotSalesCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.transaction.DateTime; + +/** + * Parses input arguments and creates a new PlotProductSalesCommand object + */ +public class PlotSalesCommandParser implements Parser { + + private static final int DEFAULT_LENGTH = 7; + private static final DateTime DEFAULT_START_DATE = + new DateTime(DateTime.DEFAULT_VALUE.minusDays(DEFAULT_LENGTH)); + + /** + * Parses the given {@code String} of arguments in the context of the PlotProductSalesCommand + * and returns a PlotProductSalesCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public PlotSalesCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_START_DATE, PREFIX_END_DATE); + Index index; + + if (anyPrefixesDuplicate(argMultimap, PREFIX_START_DATE, PREFIX_END_DATE)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + PlotSalesCommand.MESSAGE_USAGE)); + } + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, PlotSalesCommand.MESSAGE_USAGE), pe); + } + + DateTime startDateTime = getStartDateTime(argMultimap); + DateTime endDateTime = getEndDateTime(argMultimap, startDateTime); + + return new PlotSalesCommand(index, startDateTime, endDateTime); + } + + private DateTime getStartDateTime(ArgumentMultimap argMultimap) throws ParseException { + if (argMultimap.getValue(PREFIX_START_DATE).isPresent()) { + return ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_START_DATE).get()); + } else { + return DEFAULT_START_DATE; + } + } + + private DateTime getEndDateTime(ArgumentMultimap argMultimap, DateTime startDateTime) + throws ParseException { + if (argMultimap.getValue(PREFIX_END_DATE).isPresent()) { + return ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_END_DATE).get()); + } else { + return new DateTime(startDateTime.value.plusDays(DEFAULT_LENGTH)); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } +} + diff --git a/src/main/java/seedu/address/logic/parser/statistics/ProfitCommandParser.java b/src/main/java/seedu/address/logic/parser/statistics/ProfitCommandParser.java new file mode 100644 index 00000000000..04c36dd139c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/statistics/ProfitCommandParser.java @@ -0,0 +1,64 @@ +package seedu.address.logic.parser.statistics; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.statistics.ProfitCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.transaction.DateTime; + +/** + * Parses input arguments and creates a new ProfitCommand object + */ +public class ProfitCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ProfitCommand + * and returns an ProfitCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ProfitCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_START_DATE, PREFIX_END_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_START_DATE, PREFIX_END_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ProfitCommand.MESSAGE_USAGE)); + } + + if (anyPrefixesDuplicate(argMultimap, PREFIX_START_DATE, PREFIX_END_DATE)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + ProfitCommand.MESSAGE_USAGE)); + } + + DateTime startDateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_START_DATE).get()); + DateTime endDateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_END_DATE).get()); + + return new ProfitCommand(startDateTime, endDateTime); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/statistics/RevenueCommandParser.java b/src/main/java/seedu/address/logic/parser/statistics/RevenueCommandParser.java new file mode 100644 index 00000000000..5ab569080c6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/statistics/RevenueCommandParser.java @@ -0,0 +1,64 @@ +package seedu.address.logic.parser.statistics; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_START_DATE; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.statistics.RevenueCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.transaction.DateTime; + +/** + * Parses input arguments and creates a new RevenueCommand object + */ +public class RevenueCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RevenueCommand + * and returns an RevenueCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RevenueCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_START_DATE, PREFIX_END_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_START_DATE, PREFIX_END_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RevenueCommand.MESSAGE_USAGE)); + } + + if (anyPrefixesDuplicate(argMultimap, PREFIX_START_DATE, PREFIX_END_DATE)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + RevenueCommand.MESSAGE_USAGE)); + } + + DateTime startDateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_START_DATE).get()); + DateTime endDateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_END_DATE).get()); + + return new RevenueCommand(startDateTime, endDateTime); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/transaction/AddTransactionCommandParser.java b/src/main/java/seedu/address/logic/parser/transaction/AddTransactionCommandParser.java new file mode 100644 index 00000000000..676c3053402 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/transaction/AddTransactionCommandParser.java @@ -0,0 +1,103 @@ +package seedu.address.logic.parser.transaction; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MONEY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_QUANTITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TRANS_DESCRIPTION; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.transaction.AddTransactionCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.TransactionFactory; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; + +/** + * Parse input arguments and creates an AddTransactionCommand. + */ +public class AddTransactionCommandParser implements Parser { + + @Override + public AddTransactionCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_CUSTOMER, PREFIX_PRODUCT, PREFIX_DATETIME, PREFIX_QUANTITY, + PREFIX_MONEY, PREFIX_TRANS_DESCRIPTION); + + if (!arePrefixesPresent(argMultimap, PREFIX_CUSTOMER, PREFIX_PRODUCT, PREFIX_QUANTITY) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddTransactionCommand.MESSAGE_USAGE)); + } + + if (anyPrefixesDuplicate(argMultimap, PREFIX_CUSTOMER, PREFIX_PRODUCT, PREFIX_DATETIME, PREFIX_QUANTITY, + PREFIX_MONEY, PREFIX_TRANS_DESCRIPTION)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + AddTransactionCommand.MESSAGE_USAGE)); + } + + Index customerIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_CUSTOMER).get()); + Index productIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_PRODUCT).get()); + Quantity quantity = ParserUtil.parseTransactionQuantity(argMultimap.getValue(PREFIX_QUANTITY).get()); + DateTime dateTime = getDateTime(argMultimap); + Money money = getMoney(argMultimap); + Description description = getDescription(argMultimap); + + TransactionFactory transactionFactory = new TransactionFactory(customerIndex, productIndex, dateTime, + quantity, money, description); + + return new AddTransactionCommand(transactionFactory); + } + + private DateTime getDateTime(ArgumentMultimap argMultimap) throws ParseException { + if (arePrefixesPresent(argMultimap, PREFIX_DATETIME)) { + return ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATETIME).get()); + } else { + return new DateTime(DateTime.DEFAULT_VALUE); + } + } + + private Money getMoney(ArgumentMultimap argMultimap) throws ParseException { + if (arePrefixesPresent(argMultimap, PREFIX_MONEY)) { + return ParserUtil.parseMoney(argMultimap.getValue(PREFIX_MONEY).get()); + } else { + return new Money(Money.DEFAULT_VALUE); + } + } + + private Description getDescription(ArgumentMultimap argMultimap) throws ParseException { + if (arePrefixesPresent(argMultimap, PREFIX_TRANS_DESCRIPTION)) { + return ParserUtil.parseTransDescription(argMultimap.getValue(PREFIX_TRANS_DESCRIPTION).get()); + } else { + return new Description(Description.DEFAULT_VALUE); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/transaction/EditTransactionCommandParser.java b/src/main/java/seedu/address/logic/parser/transaction/EditTransactionCommandParser.java new file mode 100644 index 00000000000..948b61ff94e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/transaction/EditTransactionCommandParser.java @@ -0,0 +1,122 @@ +package seedu.address.logic.parser.transaction; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MONEY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_QUANTITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TRANS_DESCRIPTION; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.transaction.EditTransactionCommand; +import seedu.address.logic.commands.transaction.EditTransactionCommand.EditTransactionDescriptor; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditCommand object + */ +public class EditTransactionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditTransactionCommand + * and returns an EditTransactionCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditTransactionCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_CUSTOMER, PREFIX_PRODUCT, PREFIX_DATETIME, + PREFIX_QUANTITY, PREFIX_MONEY, PREFIX_TRANS_DESCRIPTION); + + if (!anyPrefixesPresent(argMultimap, PREFIX_CUSTOMER, PREFIX_PRODUCT, PREFIX_DATETIME, + PREFIX_QUANTITY, PREFIX_MONEY, PREFIX_TRANS_DESCRIPTION) + || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditTransactionCommand.MESSAGE_USAGE)); + } + + if (anyPrefixesDuplicate(argMultimap, PREFIX_CUSTOMER, PREFIX_PRODUCT, PREFIX_DATETIME, + PREFIX_QUANTITY, PREFIX_MONEY, PREFIX_TRANS_DESCRIPTION)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + EditTransactionCommand.MESSAGE_USAGE)); + } + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditTransactionCommand.MESSAGE_USAGE), pe); + } + + EditTransactionDescriptor editTransactionDescriptor = getEditTransactionDescriptor(argMultimap); + + if (!editTransactionDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditTransactionCommand.MESSAGE_NOT_EDITED); + } + + return new EditTransactionCommand(index, editTransactionDescriptor); + } + + private EditTransactionDescriptor getEditTransactionDescriptor(ArgumentMultimap argMultimap) + throws ParseException { + EditTransactionDescriptor editTransactionDescriptor = new EditTransactionDescriptor(); + if (argMultimap.getValue(PREFIX_CUSTOMER).isPresent()) { + editTransactionDescriptor.setCustomerIndex( + ParserUtil.parseIndex(argMultimap.getValue(PREFIX_CUSTOMER).get()) + ); + } + if (argMultimap.getValue(PREFIX_PRODUCT).isPresent()) { + editTransactionDescriptor.setProductIndex( + ParserUtil.parseIndex(argMultimap.getValue(PREFIX_PRODUCT).get()) + ); + } + if (argMultimap.getValue(PREFIX_DATETIME).isPresent()) { + editTransactionDescriptor.setDateTime( + ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATETIME).get()) + ); + } + if (argMultimap.getValue(PREFIX_QUANTITY).isPresent()) { + editTransactionDescriptor.setQuantity( + ParserUtil.parseTransactionQuantity(argMultimap.getValue(PREFIX_QUANTITY).get()) + ); + } + if (argMultimap.getValue(PREFIX_MONEY).isPresent()) { + editTransactionDescriptor.setMoney( + ParserUtil.parseMoney(argMultimap.getValue(PREFIX_MONEY).get()) + ); + } + if (argMultimap.getValue(PREFIX_TRANS_DESCRIPTION).isPresent()) { + editTransactionDescriptor.setDescription( + ParserUtil.parseTransDescription(argMultimap.getValue(PREFIX_TRANS_DESCRIPTION).get())); + } + return editTransactionDescriptor; + } + + /** + * Returns true if any of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } +} + diff --git a/src/main/java/seedu/address/logic/parser/transaction/FindTransactionCommandParser.java b/src/main/java/seedu/address/logic/parser/transaction/FindTransactionCommandParser.java new file mode 100644 index 00000000000..4b53e5b3373 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/transaction/FindTransactionCommandParser.java @@ -0,0 +1,106 @@ +package seedu.address.logic.parser.transaction; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MONEY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import seedu.address.logic.commands.transaction.FindTransactionCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.transaction.CustomerContainsKeywordPredicate; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.DateTimeEqualsPredicate; +import seedu.address.model.transaction.JointTransactionPredicate; +import seedu.address.model.transaction.MoneyEqualsPredicate; +import seedu.address.model.transaction.ProductContainsKeywordPredicate; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.util.Money; + +/** + * Parse input arguments and creates a new FindCommand object. + */ +public class FindTransactionCommandParser implements Parser { + + private final List> predicates = new ArrayList<>(); + + /** + * Parses the given {@code String} of arguments in the context of the FindTransactionCommand + * and returns a FindTransactionCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindTransactionCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_CUSTOMER, PREFIX_PRODUCT, PREFIX_DATETIME, PREFIX_MONEY); + + if (!anyPrefixesPresent(argMultimap, PREFIX_CUSTOMER, PREFIX_PRODUCT, PREFIX_DATETIME, PREFIX_MONEY) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindTransactionCommand.MESSAGE_USAGE)); + } + + if (anyPrefixesDuplicate(argMultimap, PREFIX_CUSTOMER, PREFIX_PRODUCT, PREFIX_DATETIME, PREFIX_MONEY)) { + throw new ParseException(String.format(MESSAGE_MULTIPLE_SAME_PREFIX, + FindTransactionCommand.MESSAGE_USAGE)); + } + + addToPredicates(argMultimap); + + return new FindTransactionCommand(new JointTransactionPredicate(predicates)); + } + + /** + * Add attributes entered by user to predicates list. + * @param argMultimap + * @throws ParseException + */ + private void addToPredicates(ArgumentMultimap argMultimap) throws ParseException { + if (anyPrefixesPresent(argMultimap, PREFIX_CUSTOMER)) { + String customerArgs = ParserUtil.parseName(argMultimap.getValue(PREFIX_CUSTOMER).get()).toString(); + String[] customerKeywords = customerArgs.split("\\s+"); + predicates.add(new CustomerContainsKeywordPredicate(Arrays.asList(customerKeywords))); + } + if (anyPrefixesPresent(argMultimap, PREFIX_PRODUCT)) { + String productArgs = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_PRODUCT).get()).toString(); + String[] productKeywords = productArgs.split("\\s+"); + predicates.add(new ProductContainsKeywordPredicate(Arrays.asList(productKeywords))); + } + if (anyPrefixesPresent(argMultimap, PREFIX_DATETIME)) { + DateTime dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATETIME).get()); + predicates.add(new DateTimeEqualsPredicate(dateTime)); + } + if (anyPrefixesPresent(argMultimap, PREFIX_MONEY)) { + Money money = ParserUtil.parseMoney(argMultimap.getValue(PREFIX_MONEY).get()); + predicates.add(new MoneyEqualsPredicate(money)); + } + } + + /** + * Returns true if any of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean anyPrefixesDuplicate(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.hasDuplicateValues(prefix)); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/transaction/UndoTransactionCommandParser.java b/src/main/java/seedu/address/logic/parser/transaction/UndoTransactionCommandParser.java new file mode 100644 index 00000000000..b5b0c2df7a8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/transaction/UndoTransactionCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser.transaction; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.transaction.UndoTransactionCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new UndoTransactionCommand object + */ +public class UndoTransactionCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the UndoTransactionCommand + * and returns a UndoTransactionCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UndoTransactionCommand parse(String args) throws ParseException { + Index index; + + try { + index = ParserUtil.parseIndex(args); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UndoTransactionCommand.MESSAGE_USAGE), pe); + } + + return new UndoTransactionCommand(index); + } + +} + diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 1a943a0781a..00000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/InventorySystem.java b/src/main/java/seedu/address/model/InventorySystem.java new file mode 100644 index 00000000000..53bf5b7e127 --- /dev/null +++ b/src/main/java/seedu/address/model/InventorySystem.java @@ -0,0 +1,249 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.UUID; + +import javafx.collections.ObservableList; + +import seedu.address.logic.commands.customer.ClearCustomerCommand; +import seedu.address.logic.commands.product.ClearProductCommand; +import seedu.address.logic.commands.transaction.ClearTransactionCommand; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.UniqueCustomerList; +import seedu.address.model.product.Product; +import seedu.address.model.product.UniqueProductList; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.transaction.UniqueTransactionList; + +/** + * Wraps all data at the address-book level + * Duplicates are not allowed (by .isSamePerson comparison) + */ +public class InventorySystem implements ReadOnlyInventorySystem { + + private final UniqueCustomerList persons; + private final UniqueProductList products; + private final UniqueTransactionList transactions; + + /* + * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ + { + transactions = new UniqueTransactionList(); + persons = new UniqueCustomerList(); + products = new UniqueProductList(); + } + + public InventorySystem() {} + + /** + * Creates an InventorySystem using the Persons and Products in the {@code toBeCopied} + */ + public InventorySystem(ReadOnlyInventorySystem toBeCopied) { + this(); + resetData(toBeCopied, ClearCustomerCommand.COMMAND_WORD); + resetData(toBeCopied, ClearProductCommand.COMMAND_WORD); + resetData(toBeCopied, ClearTransactionCommand.COMMAND_WORD); + } + + //// list overwrite operations + + /** + * Replaces the contents of the customer list with {@code customers}. + * {@code customers} must not contain duplicate customers. + */ + public void setPersons(List customers) { + this.persons.setPersons(customers); + } + + /** + * Replaces the contents of the product list with {@code products}. + * {@code products} must not contain duplicate products. + */ + public void setProducts(List products) { + this.products.setProduct(products); + } + + /** + * Replaces the contents of the transaction list with {@code transactions}. + * {@code transactions} must not contain duplicate products. + */ + public void setTransactions(List transactions) { + this.transactions.setTransaction(transactions); + } + + /** + * Resets the existing data of this {@code InventorySystem} with {@code newData}. + */ + public void resetData(ReadOnlyInventorySystem newData, String commandWord) { + requireNonNull(newData); + + if (commandWord.equals(ClearCustomerCommand.COMMAND_WORD)) { + setPersons(newData.getPersonList()); + setTransactions(newData.getTransactionList()); + } else if (commandWord.equals(ClearProductCommand.COMMAND_WORD)) { + setProducts(newData.getProductList()); + setTransactions(newData.getTransactionList()); + } else if (commandWord.equals(ClearTransactionCommand.COMMAND_WORD)) { + setTransactions(newData.getTransactionList()); + } + } + + //// customer-level operations + + /** + * Returns true if a customer with the same identity as {@code customer} exists in the address book. + */ + public boolean hasPerson(Customer customer) { + requireNonNull(customer); + return persons.contains(customer); + } + + /** + * Returns true if a product with the same identity as {@code product} exists in the product list. + */ + public boolean hasProduct(Product product) { + requireNonNull(product); + return products.contains(product); + } + + /** + * Adds a customer to the address book. + * The customer must not already exist in the address book. + */ + public void addPerson(Customer p) { + persons.add(p); + } + + /** + * Adds a product to the address book. + * The product must not already exist in the product list. + */ + public void addProduct(Product p) { + products.add(p); + } + + /** + * Finds a product by id. + * @param id the unique if for product. + * @return the product found. + */ + public Product findProductById(UUID id) { + return products.findProductById(id); + } + + /** + * Replaces the given customer {@code target} in the list with {@code editedCustomer}. + * {@code target} must exist in the address book. + * The customer identity of {@code editedCustomer} must not be the same as another existing customer + * in the address book. + */ + public void setPerson(Customer target, Customer editedCustomer) { + requireNonNull(editedCustomer); + persons.setPerson(target, editedCustomer); + } + + /** + * Replaces the given product {@code target} in the list with {@code editedProduct}. + * {@code target} must exist in the product list. + * The product identity of {@code editedProduct} must not be the same as another + * existing product in the product list. + */ + public void setProduct(Product target, Product editedProduct) { + requireNonNull(editedProduct); + products.setProduct(target, editedProduct); + } + + /** + * Replaces the given product {@code target} in the list with {@code editedProduct}. + * {@code target} must exist in the product list. + * The product identity of {@code editedProduct} must not be the same as another + * existing product in the product list. + */ + public void setTransaction(Transaction target, Transaction editedTransaction) { + requireNonNull(editedTransaction); + transactions.setTransaction(target, editedTransaction); + } + + /** + * Removes {@code key} from this {@code InventorySystem}. + * {@code key} must exist in the address book. + */ + public void removePerson(Customer key) { + persons.remove(key); + } + + ////Transaction Level operations + + /** + * Returns true if a transaction with the same identity as {@code t} exists in the system. + * @param t transaction to be checked. + * @return true if a transaction with the same identity exists in the list. + */ + public boolean hasTransaction(Transaction t) { + requireNonNull(t); + return transactions.contains(t); + } + + public void addTransaction(Transaction t) { + transactions.add(t); + } + + /** + * Removes {@code key} from this {@code InventorySystem}. + * {@code key} must exist in the address book. + */ + public void removeTransaction(Transaction t) { + transactions.remove(t); + } + + /** + * Removes {@code key} from this {@code InventorySystem}. + * {@code key} must exist in the product list. + */ + public void removeProduct(Product key) { + products.remove(key); + } + + //// util methods + + @Override + public String toString() { + return persons.asUnmodifiableObservableList().size() + " persons" + + products.asUnmodifiableObservableList().size() + " products"; + // TODO: refine later + } + + @Override + public ObservableList getPersonList() { + return persons.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getProductList() { + return products.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getTransactionList() { + return transactions.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof InventorySystem // instanceof handles nulls + && persons.equals(((InventorySystem) other).persons)); + } + + @Override + public int hashCode() { + return persons.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..5fef899b7e5 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,18 +1,24 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.UUID; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; + +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; /** * The API of the Model component. */ public interface Model { /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_PRODUCTS = unused -> true; + Predicate PREDICATE_SHOW_ALL_TRANSACTIONS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -37,51 +43,157 @@ public interface Model { /** * Returns the user prefs' address book file path. */ - Path getAddressBookFilePath(); + Path getInventorySystemFilePath(); /** * Sets the user prefs' address book file path. */ - void setAddressBookFilePath(Path addressBookFilePath); + void setInventorySystemFilePath(Path inventorySystemFilePath); + + /** + * Replaces address book data with the data in {@code inventorySystem}. + */ + void setInventorySystem(ReadOnlyInventorySystem inventorySystem, String commandWord); + + /** Returns the InventorySystem */ + ReadOnlyInventorySystem getInventorySystem(); + + /** + * Returns true if a customer with the same identity as {@code customer} exists in the address book. + */ + boolean hasPerson(Customer customer); + + /** + * Returns true if a product with the same identity as {@code product} exists in the product list. + */ + boolean hasProduct(Product product); + + /** + * Deletes the given customer. + * The customer must exist in the address book. + */ + void deletePerson(Customer target); + + /** + * Deletes the given product. + * The product must exist in the product list. + */ + void deleteProduct(Product target); + + /** + * Deletes the given transaction. + * The transaction must exist in the product list. + */ + void deleteTransaction(Transaction target); /** - * Replaces address book data with the data in {@code addressBook}. + * Adds the given customer. + * {@code customer} must not already exist in the address book. */ - void setAddressBook(ReadOnlyAddressBook addressBook); + void addPerson(Customer customer); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + /** + * Adds the given product. + * {@code product} must not already exist in the product list. + */ + void addProduct(Product product); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Find product by id. + * {@code id} the unique id. */ - boolean hasPerson(Person person); + Product findProductById(UUID id); /** - * Deletes the given person. - * The person must exist in the address book. + * Replaces the given customer {@code target} with {@code editedCustomer}. + * {@code target} must exist in the address book. + * The customer identity of {@code editedCustomer} must not be the same as another existing customer + * in the address book. */ - void deletePerson(Person target); + void setPerson(Customer target, Customer editedCustomer); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Replaces the given product {@code target} with {@code editedProduct}. + * {@code target} must exist in the product list. + * The product identity of {@code editedProduct} must not be the same as + * another existing product in the address book. */ - void addPerson(Person person); + void setProduct(Product target, Product editedProduct); /** - * Replaces the given person {@code target} with {@code editedPerson}. + * Replaces the given customer {@code target} with {@code editedCustomer}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The customer identity of {@code editedCustomer} must not be the same as another existing customer + * in the address book. + */ + void setTransaction(Transaction target, Transaction editedTransaction); + + /** Returns an unmodifiable view of the filtered customer list */ + ObservableList getFilteredCustomerList(); + + /** + * Updates the filter of the filtered customer list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + + /** + * Returns true if a transaction with the same identity as {@code transaction} exists in the address book. + */ + boolean hasTransaction(Transaction transaction); + + /** + * Adds the given transaction. + * {@code transaction} must not already exist in the address book. + */ + void addTransaction(Transaction transaction); + + /** + * Return a filtered observable transactions list. + * @param predicate specifies the matching condition. + */ + ObservableList filterTransaction(Predicate predicate); + + /** + * Updates the filter of the filtered customer list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredCustomerList(Predicate predicate); + + /** + * Updates the filter of the filtered customer list to filter by the current {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. */ - void setPerson(Person target, Person editedPerson); + void updateFilteredCustomerList(); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** + * Updates the filter of the filtered product list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredProductList(Predicate predicate); + + + /** + * Updates the filter of the filtered product list to filter by the current {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredProductList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the filter of the filtered transaction list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredTransactionList(Predicate predicate); + + /** + * Updates the filter of the filtered transaction list to filter by the current {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredTransactionList(); + + + /** Returns an unmodifiable view of the filtered product list */ + ObservableList getFilteredProductList(); + + /** Returns an unmodifiable view of the filtered product list */ + ObservableList getFilteredTransactionList(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 0650c954f5c..9d7a4b9c890 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,14 +4,19 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.UUID; import java.util.function.Predicate; import java.util.logging.Logger; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; + +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; /** * Represents the in-memory model of the address book data. @@ -19,26 +24,34 @@ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final AddressBook addressBook; + private final InventorySystem inventorySystem; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; + private final FilteredList filteredCustomers; + private final FilteredList filteredProducts; + private final FilteredList filteredTransactions; + + private Predicate customerPredicate; + private Predicate productPredicate; + private Predicate transactionPredicate; /** * Initializes a ModelManager with the given addressBook and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { + public ModelManager(ReadOnlyInventorySystem addressBook, ReadOnlyUserPrefs userPrefs) { super(); requireAllNonNull(addressBook, userPrefs); logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - this.addressBook = new AddressBook(addressBook); + this.inventorySystem = new InventorySystem(addressBook); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredCustomers = new FilteredList<>(this.inventorySystem.getPersonList()); + filteredProducts = new FilteredList<>(this.inventorySystem.getProductList()); + filteredTransactions = new FilteredList<>(this.inventorySystem.getTransactionList()); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new InventorySystem(), new UserPrefs()); } //=========== UserPrefs ================================================================================== @@ -66,67 +79,184 @@ public void setGuiSettings(GuiSettings guiSettings) { } @Override - public Path getAddressBookFilePath() { + public Path getInventorySystemFilePath() { return userPrefs.getAddressBookFilePath(); } @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); + public void setInventorySystemFilePath(Path inventorySystemFilePath) { + requireNonNull(inventorySystemFilePath); + userPrefs.setAddressBookFilePath(inventorySystemFilePath); + } + + //=========== InventorySystem ================================================================================ + + @Override + public void setInventorySystem(ReadOnlyInventorySystem inventorySystem, String commandWord) { + this.inventorySystem.resetData(inventorySystem, commandWord); + } + + @Override + public ReadOnlyInventorySystem getInventorySystem() { + return inventorySystem; + } + + @Override + public boolean hasPerson(Customer customer) { + requireNonNull(customer); + return inventorySystem.hasPerson(customer); + } + + @Override + public boolean hasProduct(Product product) { + requireNonNull(product); + return inventorySystem.hasProduct(product); } - //=========== AddressBook ================================================================================ + @Override + public Product findProductById(UUID id) { + requireNonNull(id); + return inventorySystem.findProductById(id); + } + + @Override + public void deletePerson(Customer target) { + inventorySystem.removePerson(target); + } + + @Override + public void deleteProduct(Product target) { + inventorySystem.removeProduct(target); + } + + @Override + public void addPerson(Customer customer) { + inventorySystem.addPerson(customer); + updateFilteredCustomerList(PREDICATE_SHOW_ALL_PERSONS); + } + + @Override + public void addProduct(Product product) { + inventorySystem.addProduct(product); + updateFilteredProductList(PREDICATE_SHOW_ALL_PRODUCTS); + } @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); + public boolean hasTransaction(Transaction transaction) { + requireNonNull(transaction); + return inventorySystem.hasTransaction(transaction); } @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; + public void addTransaction(Transaction transaction) { + inventorySystem.addTransaction(transaction); + updateFilteredTransactionList(PREDICATE_SHOW_ALL_TRANSACTIONS); } @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); + public void setTransaction(Transaction target, Transaction editedTransaction) { + requireAllNonNull(target, editedTransaction); + inventorySystem.setTransaction(target, editedTransaction); } @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); + public void deleteTransaction(Transaction transaction) { + inventorySystem.removeTransaction(transaction); } @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public ObservableList filterTransaction(Predicate predicate) { + requireNonNull(predicate); + FilteredList newFilteredTransactions = + new FilteredList<>(inventorySystem.getTransactionList()); + newFilteredTransactions.setPredicate(predicate); + return newFilteredTransactions; } @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public void setPerson(Customer target, Customer editedCustomer) { + requireAllNonNull(target, editedCustomer); + inventorySystem.setPerson(target, editedCustomer); + } - addressBook.setPerson(target, editedPerson); + @Override + public void setProduct(Product target, Product editedProduct) { + requireAllNonNull(target, editedProduct); + inventorySystem.setProduct(target, editedProduct); } - //=========== Filtered Person List Accessors ============================================================= + //=========== Filtered Customer List Accessors ============================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * Returns an unmodifiable view of the list of {@code Customer} backed by the internal list of * {@code versionedAddressBook} */ @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; + public ObservableList getFilteredCustomerList() { + return filteredCustomers; + } + + @Override + public ObservableList getFilteredProductList() { + return filteredProducts; } @Override - public void updateFilteredPersonList(Predicate predicate) { + public ObservableList getFilteredTransactionList() { + return filteredTransactions; + } + + @Override + public void updateFilteredCustomerList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredCustomers.setPredicate(predicate); + customerPredicate = predicate; + } + + @Override + public void updateFilteredCustomerList() { + filteredCustomers.setPredicate(customerPredicate); + } + + @Override + public void updateFilteredProductList(Predicate predicate) { + requireNonNull(predicate); + filteredProducts.setPredicate(PREDICATE_SHOW_ALL_PRODUCTS); + int fullProductListSize = getFilteredProductList().size(); + + filteredProducts.setPredicate(predicate); + productPredicate = predicate; + + SortedList sortedProduct = new SortedList<>(filteredProducts); + sortedProduct.comparatorProperty().set((o1, o2) -> { + if (o1.getProgress() - o2.getProgress() > 0) { + return 1; + } else if (o1.getProgress() == o2.getProgress()) { + return 0; + } else { + return -1; + } + }); + if (sortedProduct.size() == fullProductListSize) { + inventorySystem.setProducts(sortedProduct); + } + } + + @Override + public void updateFilteredProductList() { + filteredProducts.setPredicate(productPredicate); + } + + @Override + public void updateFilteredTransactionList(Predicate predicate) { + requireNonNull(predicate); + filteredTransactions.setPredicate(predicate); + transactionPredicate = predicate; + } + + @Override + public void updateFilteredTransactionList() { + filteredTransactions.setPredicate(transactionPredicate); } @Override @@ -143,9 +273,11 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) + return inventorySystem.equals(other.inventorySystem) && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); + && filteredCustomers.equals(other.filteredCustomers) + && filteredProducts.equals(other.filteredProducts) + && filteredTransactions.equals(other.filteredTransactions); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a29..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyInventorySystem.java b/src/main/java/seedu/address/model/ReadOnlyInventorySystem.java new file mode 100644 index 00000000000..30087cecfd8 --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyInventorySystem.java @@ -0,0 +1,21 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; + +/** + * Unmodifiable view of an address book + */ +public interface ReadOnlyInventorySystem { + + /** + * Returns an unmodifiable view of the persons list and product list. + * This list will not contain any duplicate persons or duplicate products. + */ + ObservableList getPersonList(); + ObservableList getProductList(); + ObservableList getTransactionList(); + +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 25a5fd6eab9..8dd4eeae743 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data" , "inventorysystem.json"); /** * Creates a {@code UserPrefs} with default values. @@ -56,6 +56,7 @@ public void setAddressBookFilePath(Path addressBookFilePath) { this.addressBookFilePath = addressBookFilePath; } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/customer/Address.java similarity index 75% rename from src/main/java/seedu/address/model/person/Address.java rename to src/main/java/seedu/address/model/customer/Address.java index 60472ca22a0..0c4843b24f0 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/customer/Address.java @@ -1,15 +1,17 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's address in the address book. + * Represents a Customer's address in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} */ public class Address { - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; + public static final String MESSAGE_CONSTRAINTS = + "Addresses can take any values (up to 45 characters), and it should not be blank"; + public static final String DEFAULT_VALUE = "N/A"; /* * The first character of the address must not be a whitespace, @@ -17,6 +19,8 @@ public class Address { */ public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final int MAX_VALUE = 45; + public final String value; /** @@ -34,7 +38,7 @@ public Address(String address) { * Returns true if a given string is a valid email. */ public static boolean isValidAddress(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && test.length() <= MAX_VALUE; } @Override diff --git a/src/main/java/seedu/address/model/customer/AddressContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/customer/AddressContainsKeywordsPredicate.java new file mode 100644 index 00000000000..d4c96ee6a06 --- /dev/null +++ b/src/main/java/seedu/address/model/customer/AddressContainsKeywordsPredicate.java @@ -0,0 +1,43 @@ +package seedu.address.model.customer; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Customer}'s {@code Address} matches any of the keywords given. + */ +public class AddressContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public AddressContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Customer customer) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(customer.getAddress().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddressContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((AddressContainsKeywordsPredicate) other).keywords)); // state check + } + + @Override + public String toString() { + String print = ""; + for (int i = 0; i < keywords.size(); i++) { + if (i + 1 == keywords.size()) { + print += keywords.get(i); + break; + } + print += keywords.get(i) + " or "; + } + return print; + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/customer/Customer.java similarity index 60% rename from src/main/java/seedu/address/model/person/Person.java rename to src/main/java/seedu/address/model/customer/Customer.java index 557a7a60cd5..18d479a9590 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/customer/Customer.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; @@ -6,19 +6,21 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; +import java.util.UUID; import seedu.address.model.tag.Tag; /** - * Represents a Person in the address book. + * Represents a Customer in the address book. * Guarantees: details are present and not null, field values are validated, immutable. */ -public class Person { +public class Customer { // Identity fields private final Name name; private final Phone phone; private final Email email; + private final UUID id; // Data fields private final Address address; @@ -27,8 +29,9 @@ public class Person { /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { + public Customer(Name name, Phone phone, Email email, Address address, Set tags) { requireAllNonNull(name, phone, email, address, tags); + this.id = UUID.randomUUID(); this.name = name; this.phone = phone; this.email = email; @@ -36,6 +39,23 @@ public Person(Name name, Phone phone, Email email, Address address, Set tag this.tags.addAll(tags); } + /** + * Every field must be present and not null. + */ + public Customer(UUID id, Name name, Phone phone, Email email, Address address, Set tags) { + requireAllNonNull(id, name, phone, email, address, tags); + this.id = id; + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.tags.addAll(tags); + } + + public UUID getId() { + return id; + } + public Name getName() { return name; } @@ -64,14 +84,17 @@ public Set getTags() { * Returns true if both persons of the same name have at least one other identity field that is the same. * This defines a weaker notion of equality between two persons. */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { + public boolean isSamePerson(Customer otherCustomer) { + if (otherCustomer == this) { return true; } - return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); + return otherCustomer != null + && (otherCustomer.getId().equals(getId()) + || (otherCustomer.getName().equals(getName()) + && otherCustomer.getPhone().equals(getPhone()) + && otherCustomer.getEmail().equals(getEmail()))) + && otherCustomer.getAddress().equals(getAddress()); } /** @@ -84,16 +107,16 @@ public boolean equals(Object other) { return true; } - if (!(other instanceof Person)) { + if (!(other instanceof Customer)) { return false; } - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); + Customer otherCustomer = (Customer) other; + return otherCustomer.getId().equals(getId()) + || (otherCustomer.getName().equals(getName()) + && otherCustomer.getPhone().equals(getPhone()) + && otherCustomer.getEmail().equals(getEmail())) + && otherCustomer.getAddress().equals(getAddress()); } @Override @@ -106,7 +129,7 @@ public int hashCode() { public String toString() { final StringBuilder builder = new StringBuilder(); builder.append(getName()) - .append(" Phone: ") + .append("\nPhone: ") .append(getPhone()) .append(" Email: ") .append(getEmail()) diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/customer/Email.java similarity index 84% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/seedu/address/model/customer/Email.java index a5bbe0b6a5f..51fc8aa5e39 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/customer/Email.java @@ -1,14 +1,17 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Customer's email in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { + public static final int MAX_LENGTH = 40; + + public static final String DEFAULT_VALUE = "N/A"; private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " + "and adhere to the following constraints:\n" @@ -18,7 +21,8 @@ public class Email { + "The domain name must:\n" + " - be at least 2 characters long\n" + " - start and end with alphanumeric characters\n" - + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any."; + + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any.\n" + + "3. The email must be equal or fewer than 40 characters in total"; // alphanumeric and special characters private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore @@ -44,7 +48,8 @@ public Email(String email) { * Returns if a given string is a valid email. */ public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); + return test.equals(DEFAULT_VALUE) + || (test.matches(VALIDATION_REGEX) && test.length() <= MAX_LENGTH); } @Override diff --git a/src/main/java/seedu/address/model/customer/EmailContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/customer/EmailContainsKeywordsPredicate.java new file mode 100644 index 00000000000..e39f78275e4 --- /dev/null +++ b/src/main/java/seedu/address/model/customer/EmailContainsKeywordsPredicate.java @@ -0,0 +1,43 @@ +package seedu.address.model.customer; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Customer}'s {@code Email} matches any of the keywords given. + */ +public class EmailContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public EmailContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Customer customer) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(customer.getEmail().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EmailContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((EmailContainsKeywordsPredicate) other).keywords)); // state check + } + + @Override + public String toString() { + String print = ""; + for (int i = 0; i < keywords.size(); i++) { + if (i + 1 == keywords.size()) { + print += keywords.get(i); + break; + } + print += keywords.get(i) + " or "; + } + return print; + } +} diff --git a/src/main/java/seedu/address/model/customer/JointCustomerPredicate.java b/src/main/java/seedu/address/model/customer/JointCustomerPredicate.java new file mode 100644 index 00000000000..e5ef585d470 --- /dev/null +++ b/src/main/java/seedu/address/model/customer/JointCustomerPredicate.java @@ -0,0 +1,72 @@ +package seedu.address.model.customer; + +import java.util.List; +import java.util.function.Predicate; + +/** + * Tests whether a {@code Customer}'s attributes fulfill the given properties. + */ +public class JointCustomerPredicate implements Predicate { + private final List> predicates; + + public JointCustomerPredicate(List> predicates) { + this.predicates = predicates; + } + + @Override + public boolean test(Customer customer) { + return predicates.stream() + .allMatch(predicate -> predicate.test(customer)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof JointCustomerPredicate // instanceof handles nulls + && predicates.equals(((JointCustomerPredicate) other).predicates)); // state check + } + + @Override + public String toString() { + String msg = ""; + + if (predicates.size() > 1) { + for (Predicate predicate : predicates) { + if (msg.isEmpty()) { + if (predicate instanceof NameContainsKeywordsPredicate) { + msg = String.format("No customers named %s ", predicate.toString()); + } else if (predicate instanceof AddressContainsKeywordsPredicate) { + msg = String.format("No customers staying in the area %s ", predicate.toString()); + } else if (predicate instanceof EmailContainsKeywordsPredicate) { + msg = String.format("No customers with email %s ", predicate.toString()); + } else if (predicate instanceof PhoneContainsKeywordsPredicate) { + msg = String.format("No customers with phone number %s ", predicate.toString()); + } + } else { + if (predicate instanceof NameContainsKeywordsPredicate) { + msg += String.format("named %s ", predicate.toString()); + } else if (predicate instanceof AddressContainsKeywordsPredicate) { + msg += String.format("staying in the area %s ", predicate.toString()); + } else if (predicate instanceof EmailContainsKeywordsPredicate) { + msg += String.format("with email %s ", predicate.toString()); + } else if (predicate instanceof PhoneContainsKeywordsPredicate) { + msg += String.format("with phone number %s ", predicate.toString()); + } + } + } + msg += "found!"; + } else { + Predicate predicate = predicates.get(0); + if (predicate instanceof NameContainsKeywordsPredicate) { + msg = String.format("No customers named %s found!", predicate.toString()); + } else if (predicate instanceof AddressContainsKeywordsPredicate) { + msg = String.format("No customers staying in the area %s found!", predicate.toString()); + } else if (predicate instanceof EmailContainsKeywordsPredicate) { + msg = String.format("No customers with email %s found!", predicate.toString()); + } else if (predicate instanceof PhoneContainsKeywordsPredicate) { + msg = String.format("No customers with phone number %s found!", predicate.toString()); + } + } + return msg; + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/customer/Name.java similarity index 87% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/customer/Name.java index 79244d71cf7..40ad8a2d2d9 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/customer/Name.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Customer's name in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { @@ -18,6 +18,8 @@ public class Name { */ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final int MAX_VALUE = 30; + public final String fullName; /** @@ -35,7 +37,7 @@ public Name(String name) { * Returns true if a given string is a valid name. */ public static boolean isValidName(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && test.length() <= MAX_VALUE; } diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/customer/NameContainsKeywordsPredicate.java similarity index 58% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/customer/NameContainsKeywordsPredicate.java index c9b5868427c..3472d152271 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/customer/NameContainsKeywordsPredicate.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import java.util.List; import java.util.function.Predicate; @@ -6,9 +6,9 @@ import seedu.address.commons.util.StringUtil; /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Customer}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; public NameContainsKeywordsPredicate(List keywords) { @@ -16,9 +16,9 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { + public boolean test(Customer customer) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(customer.getName().fullName, keyword)); } @Override @@ -28,4 +28,16 @@ public boolean equals(Object other) { && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check } + @Override + public String toString() { + String print = ""; + for (int i = 0; i < keywords.size(); i++) { + if (i + 1 == keywords.size()) { + print += keywords.get(i); + break; + } + print += keywords.get(i) + " or "; + } + return print; + } } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/customer/Phone.java similarity index 82% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/address/model/customer/Phone.java index 872c76b382f..901f39fbc40 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/customer/Phone.java @@ -1,18 +1,21 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Customer's phone number in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; + "Phone numbers should only contain numbers, and it should be at least 3 and at most 15 digits long"; public static final String VALIDATION_REGEX = "\\d{3,}"; + + public static final int MAX_VALUE = 15; + public final String value; /** @@ -30,7 +33,7 @@ public Phone(String phone) { * Returns true if a given string is a valid phone number. */ public static boolean isValidPhone(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && test.length() <= MAX_VALUE; } @Override diff --git a/src/main/java/seedu/address/model/customer/PhoneContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/customer/PhoneContainsKeywordsPredicate.java new file mode 100644 index 00000000000..1fd97c8e3a8 --- /dev/null +++ b/src/main/java/seedu/address/model/customer/PhoneContainsKeywordsPredicate.java @@ -0,0 +1,43 @@ +package seedu.address.model.customer; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Customer}'s {@code Name} matches any of the keywords given. + */ +public class PhoneContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public PhoneContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Customer customer) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(customer.getPhone().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PhoneContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((PhoneContainsKeywordsPredicate) other).keywords)); // state check + } + + @Override + public String toString() { + String print = ""; + for (int i = 0; i < keywords.size(); i++) { + if (i + 1 == keywords.size()) { + print += keywords.get(i); + break; + } + print += keywords.get(i) + " or "; + } + return print; + } +} diff --git a/src/main/java/seedu/address/model/customer/UniqueCustomerList.java b/src/main/java/seedu/address/model/customer/UniqueCustomerList.java new file mode 100644 index 00000000000..c0aa85f894b --- /dev/null +++ b/src/main/java/seedu/address/model/customer/UniqueCustomerList.java @@ -0,0 +1,137 @@ +package seedu.address.model.customer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.customer.exceptions.DuplicatePersonException; +import seedu.address.model.customer.exceptions.PersonNotFoundException; + +/** + * A list of persons that enforces uniqueness between its elements and does not allow nulls. + * A customer is considered unique by comparing using {@code Customer#isSamePerson(Customer)}. As such, + * adding and updating of persons uses Customer#isSamePerson(Customer) for equality so as to ensure that the customer + * being added or updated is unique in terms of identity in the UniqueCustomerList. However, the removal of a customer + * uses Customer#equals(Object) so as to ensure that the customer with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Customer#isSamePerson(Customer) + */ +public class UniqueCustomerList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent customer as the given argument. + */ + public boolean contains(Customer toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSamePerson); + } + + /** + * Adds a customer to the list. + * The customer must not already exist in the list. + */ + public void add(Customer toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePersonException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the customer {@code target} in the list with {@code editedCustomer}. + * {@code target} must exist in the list. + * The customer identity of {@code editedCustomer} must not be the same as another existing customer in the list. + */ + public void setPerson(Customer target, Customer editedCustomer) { + requireAllNonNull(target, editedCustomer); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + if (!target.isSamePerson(editedCustomer) && contains(editedCustomer)) { + throw new DuplicatePersonException(); + } + + internalList.set(index, editedCustomer); + } + + /** + * Removes the equivalent customer from the list. + * The customer must exist in the list. + */ + public void remove(Customer toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new PersonNotFoundException(); + } + } + + public void setPersons(UniqueCustomerList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code customers}. + * {@code customers} must not contain duplicate customers. + */ + public void setPersons(List customers) { + requireAllNonNull(customers); + if (!personsAreUnique(customers)) { + throw new DuplicatePersonException(); + } + + internalList.setAll(customers); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueCustomerList // instanceof handles nulls + && internalList.equals(((UniqueCustomerList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code customers} contains only unique customers. + */ + private boolean personsAreUnique(List customers) { + for (int i = 0; i < customers.size() - 1; i++) { + for (int j = i + 1; j < customers.size(); j++) { + if (customers.get(i).isSamePerson(customers.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/customer/exceptions/DuplicatePersonException.java similarity index 86% rename from src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java rename to src/main/java/seedu/address/model/customer/exceptions/DuplicatePersonException.java index d7290f59442..e6242b68d06 100644 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ b/src/main/java/seedu/address/model/customer/exceptions/DuplicatePersonException.java @@ -1,4 +1,4 @@ -package seedu.address.model.person.exceptions; +package seedu.address.model.customer.exceptions; /** * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same diff --git a/src/main/java/seedu/address/model/customer/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/customer/exceptions/PersonNotFoundException.java new file mode 100644 index 00000000000..40f33282dd0 --- /dev/null +++ b/src/main/java/seedu/address/model/customer/exceptions/PersonNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.customer.exceptions; + +/** + * Signals that the operation is unable to find the specified customer. + */ +public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca7..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/product/CostPrice.java b/src/main/java/seedu/address/model/product/CostPrice.java new file mode 100644 index 00000000000..df5b021af9e --- /dev/null +++ b/src/main/java/seedu/address/model/product/CostPrice.java @@ -0,0 +1,69 @@ +package seedu.address.model.product; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Product's cost price in the product list + * Guarantees: immutable; is valid as declared in {@link #isValidPrice(String)} + */ +public class CostPrice { + + public static final String MESSAGE_CONSTRAINTS = + "Price can take any positive integer values (up to 1000000), and it should not be blank"; + + /* + * There must be one or more digits entered. + */ + public static final String VALIDATION_REGEX = "^\\d{1,7}$"; + + public static final int MAX_VALUE = 1000000; + public static final int MIN_VALUE = 1; + + public final String value; + + /** + * Constructs an {@code Price}. + * + * @param price A valid price. + */ + public CostPrice(String price) { + requireNonNull(price); + checkArgument(isValidPrice(price), MESSAGE_CONSTRAINTS); + // remove leading zeroes + value = price.replaceFirst("^0+(?!$)", ""); + } + + /** + * Returns true if a given string is a valid price. + */ + public static boolean isValidPrice(String test) { + if (test.matches(VALIDATION_REGEX)) { + try { + int value = Integer.parseInt(test); + return value <= MAX_VALUE && value >= MIN_VALUE; + } catch (NumberFormatException e) { + return false; + } + } + return false; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CostPrice // instanceof handles nulls + && value.equals(((CostPrice) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/product/DescriptionContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/product/DescriptionContainsKeywordsPredicate.java new file mode 100644 index 00000000000..6670200d8f9 --- /dev/null +++ b/src/main/java/seedu/address/model/product/DescriptionContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.product; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Product}'s {@code Name} matches any of the keywords given. + */ +public class DescriptionContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public DescriptionContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Product product) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(product.getDescription().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DescriptionContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((DescriptionContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/product/Price.java b/src/main/java/seedu/address/model/product/Price.java new file mode 100644 index 00000000000..5a98e3b1476 --- /dev/null +++ b/src/main/java/seedu/address/model/product/Price.java @@ -0,0 +1,69 @@ +package seedu.address.model.product; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Product's price in the product list + * Guarantees: immutable; is valid as declared in {@link #isValidPrice(String)} + */ +public class Price { + + public static final String MESSAGE_CONSTRAINTS = + "Price can take any positive integer values (up to 1000000), and it should not be blank"; + + /* + * There must be one or more digits entered. + */ + public static final String VALIDATION_REGEX = "^\\d{1,7}$"; + + public static final int MAX_VALUE = 1000000; + public static final int MIN_VALUE = 1; + + public final String value; + + /** + * Constructs an {@code Price}. + *th + * @param price A valid price. + */ + public Price(String price) { + requireNonNull(price); + checkArgument(isValidPrice(price), MESSAGE_CONSTRAINTS); + // remove leading zeroes + value = price.replaceFirst("^0+(?!$)", ""); + } + + /** + * Returns true if a given string is a valid price. + */ + public static boolean isValidPrice(String test) { + if (test.matches(VALIDATION_REGEX)) { + try { + int value = Integer.parseInt(test); + return value <= MAX_VALUE && value >= MIN_VALUE; + } catch (NumberFormatException e) { + return false; + } + } + return false; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Price // instanceof handles nulls + && value.equals(((Price) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/product/Product.java b/src/main/java/seedu/address/model/product/Product.java new file mode 100644 index 00000000000..4067f95791a --- /dev/null +++ b/src/main/java/seedu/address/model/product/Product.java @@ -0,0 +1,202 @@ +package seedu.address.model.product; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import seedu.address.model.transaction.Transaction; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; +import seedu.address.model.util.QuantityThreshold; + +/** + * Represents a Product in the product list. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Product { + + // Identity fields + private final Description description; + private final CostPrice costPrice; + private final Price price; + + // Data fields + private final Quantity quantity; + private final Money money; + private final UUID id; + + private QuantityThreshold threshold; + private double progress; + + /** + * Every field must be present and not null. + */ + public Product(Description description, CostPrice costPrice, Price price, Quantity quantity, + Money money, QuantityThreshold threshold, double progress) { + requireAllNonNull(description, costPrice, price, quantity); + this.id = UUID.randomUUID(); + this.description = description; + this.costPrice = costPrice; + this.price = price; + this.quantity = quantity; + this.money = money; + this.threshold = threshold; + this.progress = progress; + } + + /** + * Every field must be present and not null. + */ + public Product(UUID id, Description description, CostPrice costPrice, Price price, Quantity quantity, + Money money, QuantityThreshold threshold, double progress) { + requireAllNonNull(id, description, costPrice, price, quantity); + this.id = id; + this.description = description; + this.costPrice = costPrice; + this.price = price; + this.quantity = quantity; + this.money = money; + this.threshold = threshold; + this.progress = progress; + } + + public UUID getId() { + return id; + } + + public Description getDescription() { + return description; + } + + public CostPrice getCostPrice() { + return costPrice; + } + + public Price getPrice() { + return price; + } + + public Quantity getQuantity() { + return quantity; + } + + public Money getMoney() { + return money; + } + + public QuantityThreshold getThreshold() { + return threshold; + } + + public void setThreshold(String quantityThreshold) { + this.threshold = new QuantityThreshold(quantityThreshold); + } + + public double getProgress() { + return progress; + } + + public void setProgress(double progress) { + this.progress = progress; + } + + public int getQuantitySold(List transactions) { + int count = 0; + + for (int i = 0; i < transactions.size(); i++) { + Transaction transaction = transactions.get(i); + + if (transaction.getProduct().equals(this)) { + int quantity = transaction.getQuantity().getValue(); + count += quantity; + } + } + return count; + } + + public int getProfit(List transactions) { + int profit = 0; + + for (int i = 0; i < transactions.size(); i++) { + Transaction transaction = transactions.get(i); + + if (transaction.getProduct().equals(this)) { + int price = transaction.getMoney().value; + int quantity = transaction.getQuantity().getValue(); + int costPrice = Integer.parseInt(transaction.getProduct().getCostPrice().value); + profit += (price - costPrice * quantity); + } + } + return profit; + } + + /** + * Returns true if both products have the same identity and data fields. + */ + public boolean isSameProduct(Product otherProduct) { + if (otherProduct == this) { + return true; + } + + return otherProduct != null + && otherProduct.getId().equals(getId()) + || (otherProduct.getDescription().equals(getDescription()) + && otherProduct.getCostPrice().equals(getCostPrice()) + && otherProduct.getPrice().equals(getPrice())); + } + + /** + * Returns true if the product's quantity is below or equal its threshold. + */ + public boolean reachesThreshold() { + return this.quantity.getValue() <= this.threshold.value; + } + + /** + * Returns true if both products have the same identity and data fields. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Product)) { + return false; + } + + Product otherProduct = (Product) other; + return otherProduct.getId().equals(getId()) + || (otherProduct.getDescription().equals(getDescription()) + && otherProduct.getCostPrice().equals(getCostPrice()) + && otherProduct.getPrice().equals(getPrice())) + && otherProduct.getQuantity().equals(getQuantity()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(description, costPrice, price, quantity, money); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getDescription()) + .append("\nCost Price: $") + .append(getCostPrice()) + .append(" Price: $") + .append(getPrice()) + .append(" Quantity: ") + .append(getQuantity()) + .append(" Sales: $") + .append(getMoney()) + .append(" Threshold: ") + .append(getThreshold()); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/product/ProductQuantity.java b/src/main/java/seedu/address/model/product/ProductQuantity.java new file mode 100644 index 00000000000..8c06263c537 --- /dev/null +++ b/src/main/java/seedu/address/model/product/ProductQuantity.java @@ -0,0 +1,115 @@ +package seedu.address.model.product; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import seedu.address.model.util.Quantity; + +/** + * Represents a Product's quantity in the prodct list + * Guarantees: immutable; + */ +public class ProductQuantity implements Quantity { + + public static final int MIN_VALUE = 0; + + public static final String MESSAGE_CONSTRAINTS_VALUE = "The numeric value of Product Quantity must be " + + "larger than or equal to " + MIN_VALUE + + " and smaller than or equal to " + MAX_VALUE; + + public final int value; + + /** + * Constructs an {@code Quantity}. + * + * @param quantity A valid quantity in string type. + */ + public ProductQuantity(String quantity) { + requireNonNull(quantity); + checkArgument(isValidFormat(quantity), MESSAGE_CONSTRAINTS_FORMAT); + int numericValue = Integer.parseInt(quantity); + checkArgument(isValidValue(numericValue), MESSAGE_CONSTRAINTS_VALUE); + value = numericValue; + } + + public ProductQuantity(int q) { + requireNonNull(q); + checkArgument(isValidValue(q), MESSAGE_CONSTRAINTS_VALUE); + value = q; + } + + public int getValue() { + return value; + } + + /** + * Returns true if a given string matches the regex of a valid product quantity. + */ + public static boolean isValidFormat(String test) { + if (test.matches(VALIDATION_REGEX)) { + try { + int value = Integer.parseInt(test); + return isValidValue(value); + } catch (NumberFormatException e) { + return false; + } + } + return false; + } + + /** + * Returns true if a integer is a valid value for quantity. + */ + public static boolean isValidValue(int test) { + return test >= MIN_VALUE && test <= MAX_VALUE; + } + + /** + * Returns a new quantity whose value is the difference between this value and the + * other's value. + * @param q other quantity. + * @return new quantity. + */ + public Quantity minus(Quantity q) { + requireNonNull(q); + int newValue = value - q.getValue(); + return new ProductQuantity(newValue); + } + + /** + * Returns a new quantity whose value is the summation between this value and the + * other's value. + * @param q other quantity. + * @return new quantity. + */ + public Quantity plus(Quantity q) { + requireNonNull(q); + int newValue = value + q.getValue(); + return new ProductQuantity(newValue); + } + + public String toString() { + return String.valueOf(value); + } + + /** + * Returns true if two product quantities are equal in terms of value. + */ + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Quantity // instanceof handles nulls + && value == ((Quantity) other).getValue()); // state check + } + + @Override + public int hashCode() { + return Integer.hashCode(value); + } + + @Override + public int compareTo(Quantity q) { + return value - q.getValue(); + } + + +} diff --git a/src/main/java/seedu/address/model/product/UniqueProductList.java b/src/main/java/seedu/address/model/product/UniqueProductList.java new file mode 100644 index 00000000000..bf5d869b1dc --- /dev/null +++ b/src/main/java/seedu/address/model/product/UniqueProductList.java @@ -0,0 +1,156 @@ +package seedu.address.model.product; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.product.exceptions.DuplicateProductException; +import seedu.address.model.product.exceptions.ProductNotFoundException; + +/** + * A list of products that enforces uniqueness between its elements and does not allow nulls. + * A product is considered unique by comparing using {@code Product#isSameProduct(Product)}. + * As such, adding and updating of products uses Product#isSameProduct(Product) for + * equality so as to ensure that the product being added or updated is + * unique in terms of identity in the UniqueProductList. However, the + * removal of a product uses Product#equals(Object) so + * as to ensure that the product with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Product#isSameProduct(Product) + */ +public class UniqueProductList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent product as the given argument. + */ + public boolean contains(Product toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameProduct); + } + + /** + * Adds a product to the list. + * The product must not already exist in the list. + */ + public void add(Product toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateProductException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the product {@code target} in the list with {@code editedProduct}. + * {@code target} must exist in the list. + * The product identity of {@code editedProduct} must not be the same as another existing product in the list. + */ + public void setProduct(Product target, Product editedProduct) { + requireAllNonNull(target, editedProduct); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ProductNotFoundException(); + } + + if (!target.isSameProduct(editedProduct) && contains(editedProduct)) { + throw new DuplicateProductException(); + } + + internalList.set(index, editedProduct); + } + + public void setProduct(UniqueProductList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code products}. + * {@code products} must not contain duplicate products. + */ + public void setProduct(List products) { + requireAllNonNull(products); + if (!productsAreUnique(products)) { + throw new DuplicateProductException(); + } + + internalList.setAll(products); + } + + /** + * Removes the equivalent product from the list. + * The product must exist in the list. + */ + public void remove(Product toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new ProductNotFoundException(); + } + } + + /** + * Find a product by UUID. + * @param id the unique id. + * @return a specific product with matching id. + */ + public Product findProductById(UUID id) { + for (Product p: internalList) { + System.out.println(id.toString()); + System.out.println(p.getId().toString()); + if (p.getId().equals(id)) { + return p; + } + } + throw new ProductNotFoundException(); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueProductList // instanceof handles nulls + && internalList.equals(((UniqueProductList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code products} contains only unique products. + */ + private boolean productsAreUnique(List products) { + for (int i = 0; i < products.size() - 1; i++) { + for (int j = i + 1; j < products.size(); j++) { + if (products.get(i).isSameProduct(products.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/product/exceptions/DuplicateProductException.java b/src/main/java/seedu/address/model/product/exceptions/DuplicateProductException.java new file mode 100644 index 00000000000..9ddac32c47a --- /dev/null +++ b/src/main/java/seedu/address/model/product/exceptions/DuplicateProductException.java @@ -0,0 +1,11 @@ +package seedu.address.model.product.exceptions; + +/** + * Signals that the operation will result in duplicate Products (Products are + * considered duplicates if they have the same identity). + */ +public class DuplicateProductException extends RuntimeException { + public DuplicateProductException() { + super("Operation would result in duplicate products"); + } +} diff --git a/src/main/java/seedu/address/model/product/exceptions/ProductNotFoundException.java b/src/main/java/seedu/address/model/product/exceptions/ProductNotFoundException.java new file mode 100644 index 00000000000..20e9b4adb40 --- /dev/null +++ b/src/main/java/seedu/address/model/product/exceptions/ProductNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.address.model.product.exceptions; + +/** + * Signals that the operation is unable to find the specified product. + */ +public class ProductNotFoundException extends RuntimeException {} + diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index b0ea7e7dad7..6f76bccc063 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -9,9 +9,12 @@ */ public class Tag { - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; + public static final String MESSAGE_CONSTRAINTS = + "Tags names should be alphanumeric and at most 15 characters"; public static final String VALIDATION_REGEX = "\\p{Alnum}+"; + public static final int MAX_VALUE = 15; + public final String tagName; /** @@ -29,7 +32,7 @@ public Tag(String tagName) { * Returns true if a given string is a valid tag name. */ public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && test.length() <= MAX_VALUE; } @Override diff --git a/src/main/java/seedu/address/model/transaction/CustomerContainsKeywordPredicate.java b/src/main/java/seedu/address/model/transaction/CustomerContainsKeywordPredicate.java new file mode 100644 index 00000000000..67305fad5ec --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/CustomerContainsKeywordPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.transaction; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Transactions}'s {@code Customer} matches any of the keywords given. + */ +public class CustomerContainsKeywordPredicate implements Predicate { + private final List keywords; + + public CustomerContainsKeywordPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Transaction transaction) { + return keywords.stream() + .anyMatch(keyword -> + StringUtil.containsWordIgnoreCase(transaction.getCustomer().getName().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CustomerContainsKeywordPredicate // instanceof handles nulls + && keywords.equals(((CustomerContainsKeywordPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/transaction/DateTime.java b/src/main/java/seedu/address/model/transaction/DateTime.java new file mode 100644 index 00000000000..f627e47adf8 --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/DateTime.java @@ -0,0 +1,139 @@ +package seedu.address.model.transaction; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents the date time of a transaction. + * Guarantees: immutable; is valid as declared in {@link #isValidDateTime(String)} + */ +public class DateTime { + + public static final String MESSAGE_CONSTRAINTS = "DateTime should be in yyyy-mm-dd hh:mm format, " + + "and it should not be in future"; + public static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter + .ofPattern("uuuu-MM-dd HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + /* + * There must be one or more digits entered. + */ + public static final String VALIDATION_REGEX = "(\\d{4}-\\d{2}-\\d{2})\\s+\\d{2}:\\d{2}"; + public static final ZoneId DEFAULT_ZONE = ZoneId.of("Asia/Singapore"); + public static final LocalDateTime DEFAULT_VALUE = LocalDateTime.now(DEFAULT_ZONE); + + public final LocalDateTime value; + + /** + * Constructs an {@code DateTime}. + * + * @param dateTime A valid dateTime string. + */ + public DateTime(String dateTime) { + requireNonNull(dateTime); + checkArgument(isValidDateTime(dateTime), MESSAGE_CONSTRAINTS); + value = LocalDateTime.parse(dateTime, DATE_TIME_FORMAT); + } + + /** + * Constructs an {@code DateTime}. + * + * @param dateTime A valid LocalDateTime format. + */ + public DateTime(LocalDateTime dateTime) { + requireNonNull(dateTime); + value = dateTime; + } + + /** + * Returns true if a given string is a valid sales. + */ + public static boolean isValidDateTime(String test) { + if (!test.matches(VALIDATION_REGEX)) { + return false; + } + try { + LocalDateTime dateTime = LocalDateTime.parse(test, DATE_TIME_FORMAT); + if (dateTime.compareTo(DEFAULT_VALUE) > 0) { + return false; + } + } catch (Exception e) { + return false; + } + return true; + } + + /** + * Returns true if the current date time is before the other date time. + */ + public boolean isBefore(DateTime other) { + requireNonNull(other); + return value.isBefore(other.value); + } + + /** + * Returns true if the current date time is after the other date time. + */ + public boolean isAfter(DateTime other) { + requireNonNull(other); + LocalDate a = value.toLocalDate(); + a.atStartOfDay(); + return value.isAfter(other.value); + } + + /** + * Returns true if the current date time is on the same day with other date time. + */ + public boolean isOnSameDay(DateTime other) { + return value.toLocalDate().equals(other.value.toLocalDate()); + } + + /** + * Generate a range of dates. + * @param startDateTime the start date. + * @param endDateTime the end date. + * @return a list of dates between start and end. + */ + public static List populateDates(DateTime startDateTime, DateTime endDateTime) { + List dateTimes = new ArrayList<>(); + LocalDate localDate = startDateTime.value.toLocalDate(); + while (localDate.isBefore(endDateTime.value.toLocalDate().plusDays(1))) { + dateTimes.add(new DateTime(localDate.atStartOfDay())); + localDate = localDate.plusDays(1); + } + return dateTimes; + } + + @Override + public String toString() { + return value.format(DATE_TIME_FORMAT); + } + + /** + * Returns the string representation of only year and date. + */ + public String toDateString() { + return value.toLocalDate().toString(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DateTime // instanceof handles nulls + && value.equals(((DateTime) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/transaction/DateTimeEqualsPredicate.java b/src/main/java/seedu/address/model/transaction/DateTimeEqualsPredicate.java new file mode 100644 index 00000000000..9b4e5176b61 --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/DateTimeEqualsPredicate.java @@ -0,0 +1,26 @@ +package seedu.address.model.transaction; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Transactions}'s {@code DateTime} matches the DateTime object given. + */ +public class DateTimeEqualsPredicate implements Predicate { + private final DateTime targetDateTime; + + public DateTimeEqualsPredicate(DateTime targetDateTime) { + this.targetDateTime = targetDateTime; + } + + @Override + public boolean test(Transaction transaction) { + return transaction.getDateTime().equals(targetDateTime); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DateTimeEqualsPredicate // instanceof handles nulls + && targetDateTime.equals(((DateTimeEqualsPredicate) other).targetDateTime)); // state check + } +} diff --git a/src/main/java/seedu/address/model/transaction/DateTimeInRangePredicate.java b/src/main/java/seedu/address/model/transaction/DateTimeInRangePredicate.java new file mode 100644 index 00000000000..2073ce60845 --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/DateTimeInRangePredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.transaction; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Transactions}'s {@code DateTime} matches the DateTime object given. + */ +public class DateTimeInRangePredicate implements Predicate { + private final DateTime startDateTime; + private final DateTime endDateTime; + + public DateTimeInRangePredicate(DateTime startDateTime, DateTime endDateTime) { + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + } + + @Override + public boolean test(Transaction transaction) { + DateTime transactionDate = transaction.getDateTime(); + return (transactionDate.isAfter(startDateTime) || transactionDate.equals(startDateTime)) + && (transactionDate.isBefore(endDateTime) || transactionDate.equals(endDateTime)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DateTimeInRangePredicate // instanceof handles nulls + && startDateTime.equals(((DateTimeInRangePredicate) other).startDateTime) + && startDateTime.equals(((DateTimeInRangePredicate) other).endDateTime)); // state check + } +} diff --git a/src/main/java/seedu/address/model/transaction/JointTransactionPredicate.java b/src/main/java/seedu/address/model/transaction/JointTransactionPredicate.java new file mode 100644 index 00000000000..48742669d33 --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/JointTransactionPredicate.java @@ -0,0 +1,28 @@ +package seedu.address.model.transaction; + +import java.util.List; +import java.util.function.Predicate; + +/** + * Tests whether a {@code Transaction}'s attributes fulfill the given properties. + */ +public class JointTransactionPredicate implements Predicate { + private final List> predicates; + + public JointTransactionPredicate(List> predicates) { + this.predicates = predicates; + } + + @Override + public boolean test(Transaction transaction) { + return predicates.stream() + .allMatch(predicate -> predicate.test(transaction)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof JointTransactionPredicate // instanceof handles nulls + && predicates.equals(((JointTransactionPredicate) other).predicates)); // state check + } +} diff --git a/src/main/java/seedu/address/model/transaction/MoneyEqualsPredicate.java b/src/main/java/seedu/address/model/transaction/MoneyEqualsPredicate.java new file mode 100644 index 00000000000..7b2cdff5e0b --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/MoneyEqualsPredicate.java @@ -0,0 +1,28 @@ +package seedu.address.model.transaction; + +import java.util.function.Predicate; + +import seedu.address.model.util.Money; + +/** + * Tests that a {@code Transactions}'s {@code Money} matches the Money object given. + */ +public class MoneyEqualsPredicate implements Predicate { + private final Money targetMoney; + + public MoneyEqualsPredicate(Money targetMoney) { + this.targetMoney = targetMoney; + } + + @Override + public boolean test(Transaction transaction) { + return transaction.getMoney().equals(targetMoney); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MoneyEqualsPredicate // instanceof handles nulls + && targetMoney.equals(((MoneyEqualsPredicate) other).targetMoney)); // state check + } +} diff --git a/src/main/java/seedu/address/model/transaction/ProductContainsKeywordPredicate.java b/src/main/java/seedu/address/model/transaction/ProductContainsKeywordPredicate.java new file mode 100644 index 00000000000..9506d5d18c9 --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/ProductContainsKeywordPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.transaction; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Transaction}'s {@code Product} matches any of the keywords given. + */ +public class ProductContainsKeywordPredicate implements Predicate { + private final List keywords; + + public ProductContainsKeywordPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Transaction transaction) { + return keywords.stream() + .anyMatch(keyword -> + StringUtil.containsWordIgnoreCase(transaction.getProduct().getDescription().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ProductContainsKeywordPredicate // instanceof handles nulls + && keywords.equals(((ProductContainsKeywordPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/transaction/ProductIdEqualsPredicate.java b/src/main/java/seedu/address/model/transaction/ProductIdEqualsPredicate.java new file mode 100644 index 00000000000..491e8fb0b3c --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/ProductIdEqualsPredicate.java @@ -0,0 +1,27 @@ +package seedu.address.model.transaction; + +import java.util.UUID; +import java.util.function.Predicate; + +/** + * Tests that a {@code Transaction}'s {@code Product} matches the id given. + */ +public class ProductIdEqualsPredicate implements Predicate { + private final UUID id; + + public ProductIdEqualsPredicate(UUID id) { + this.id = id; + } + + @Override + public boolean test(Transaction transaction) { + return id.equals(transaction.getProductId()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ProductIdEqualsPredicate // instanceof handles nulls + && id.equals(((ProductIdEqualsPredicate) other).id)); // state check + } +} diff --git a/src/main/java/seedu/address/model/transaction/Transaction.java b/src/main/java/seedu/address/model/transaction/Transaction.java new file mode 100644 index 00000000000..6878ce2bb68 --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/Transaction.java @@ -0,0 +1,127 @@ +package seedu.address.model.transaction; + +import java.util.UUID; + +import javafx.scene.chart.XYChart; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; + +/** + * Represents a Transaction in the system. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Transaction { + + // Identity fields + private final Customer customer; + private final Product product; + private final UUID productId; + private final UUID customerId; + private final DateTime dateTime; + + // Data fields + private final Money money; + private final Quantity quantity; + private final Description description; + + + public Transaction(Customer customer, Product product, UUID customerId, UUID productId, + DateTime dateTime, Quantity quantity, Money money, Description description) { + this.customer = customer; + this.product = product; + this.productId = productId; + this.customerId = customerId; + this.quantity = quantity; + this.money = money; + this.dateTime = dateTime; + this.description = description; + } + + public Customer getCustomer() { + return customer; + } + + public Product getProduct() { + return product; + } + + public UUID getProductId() { + return productId; + } + + public UUID getCustomerId() { + return customerId; + } + + public DateTime getDateTime() { + return dateTime; + } + + public Quantity getQuantity() { + return quantity; + } + + public Money getMoney() { + return money; + } + + public Description getDescription() { + return description; + } + + /** + * Returns true if any two transactions have the same customer, products, and dateTime. + * This defines a weaker notion of equality. + * @param otherTransaction other transaction. + * @return true if these two transactions have the same customer, products, and dateTime. + */ + public boolean isSameTransaction(Transaction otherTransaction) { + if (otherTransaction == this) { + return true; + } + + return otherTransaction != null + && otherTransaction.getCustomerId().equals(getCustomerId()) + && otherTransaction.getProductId().equals(getProductId()) + && otherTransaction.getDateTime().equals(getDateTime()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Transaction)) { + return false; + } + + Transaction otherTransaction = (Transaction) other; + + return otherTransaction.getCustomerId().equals(getCustomerId()) + && otherTransaction.getProductId().equals(getProductId()) + && otherTransaction.getDateTime().equals(getDateTime()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getCustomer().getName()) + .append(" bought ") + .append(getProduct().getDescription()) + .append("\nDate/ time: ") + .append(getDateTime()) + .append(" Quantity: ") + .append(getQuantity()) + .append(" Amount: ") + .append(getMoney()); + return builder.toString(); + } + + public XYChart.Data toData() { + return new XYChart.Data<>(dateTime.value.toLocalDate().toString(), quantity.getValue()); + } +} diff --git a/src/main/java/seedu/address/model/transaction/TransactionFactory.java b/src/main/java/seedu/address/model/transaction/TransactionFactory.java new file mode 100644 index 00000000000..b592a493bc6 --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/TransactionFactory.java @@ -0,0 +1,113 @@ +package seedu.address.model.transaction; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.UUID; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; + +/** + * Creates a transaction with the customer and product index. + */ +public class TransactionFactory { + private final Index customerIndex; + private final Index productIndex; + private final DateTime dateTime; + private final Quantity quantity; + private final Money money; + private final Description description; + + public TransactionFactory(Index customerIndex, Index productIndex, DateTime dateTime, + Quantity quantity, Money money, Description description) { + this.customerIndex = customerIndex; + this.productIndex = productIndex; + this.quantity = quantity; + this.money = money; + this.dateTime = dateTime; + this.description = description; + } + + /** + * Creates a transaction with the found product and customer. + * @param model the model manager. + * @return created transaction. + */ + public Transaction createTransaction(Model model) throws CommandException { + requireNonNull(model); + Money updatedMoney; + + List customerList = model.getFilteredCustomerList(); + List productList = model.getFilteredProductList(); + + if (customerIndex.getZeroBased() >= customerList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + if (productIndex.getZeroBased() >= productList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX); + } + + Customer customer = model.getFilteredCustomerList().get(customerIndex.getZeroBased()); + Product product = model.getFilteredProductList().get(productIndex.getZeroBased()); + UUID customerId = model.getFilteredCustomerList().get(customerIndex.getZeroBased()).getId(); + UUID productId = model.getFilteredProductList().get(productIndex.getZeroBased()).getId(); + + if (money.value == Money.DEFAULT_VALUE) { + updatedMoney = new Money(Integer.parseInt(product.getPrice().value) * quantity.getValue()); + } else { + updatedMoney = money; + } + return new Transaction(customer, product, customerId, productId, dateTime, quantity, updatedMoney, description); + } + + public Index getProductIndex() { + return productIndex; + } + + public Index getCustomerIndex() { + return customerIndex; + } + + public Quantity getQuantity() { + return quantity; + } + + public Money getMoney() { + return money; + } + + public DateTime getDateTime() { + return dateTime; + } + + public Description getDescription() { + return description; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof TransactionFactory)) { + return false; + } + + TransactionFactory otherTransactionFactory = (TransactionFactory) other; + return otherTransactionFactory.customerIndex.equals(customerIndex) + && otherTransactionFactory.productIndex.equals(productIndex) + && otherTransactionFactory.dateTime.equals(dateTime) + && otherTransactionFactory.money.equals(money) + && otherTransactionFactory.quantity.equals(quantity) + && otherTransactionFactory.description.equals(description); + } +} diff --git a/src/main/java/seedu/address/model/transaction/TransactionQuantity.java b/src/main/java/seedu/address/model/transaction/TransactionQuantity.java new file mode 100644 index 00000000000..7ace40875e7 --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/TransactionQuantity.java @@ -0,0 +1,113 @@ +package seedu.address.model.transaction; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import seedu.address.model.util.Quantity; + +/** + * Represents a Transaction's quantity in the transaction list + * Guarantees: immutable; + */ +public class TransactionQuantity implements Quantity { + + public static final int MIN_VALUE = 1; + + public static final String MESSAGE_CONSTRAINTS_VALUE = "The numeric value of Transaction Quantity must be " + + "larger than or equal to " + MIN_VALUE + + " and smaller than or equal to " + MAX_VALUE; + + public final int value; + + /** + * Constructs an {@code Quantity}. + * + * @param quantity A valid quantity in string type. + */ + public TransactionQuantity(String quantity) { + requireNonNull(quantity); + checkArgument(isValidFormat(quantity), MESSAGE_CONSTRAINTS_FORMAT); + int numericValue = Integer.parseInt(quantity); + checkArgument(isValidValue(numericValue), MESSAGE_CONSTRAINTS_VALUE); + value = numericValue; + } + + public TransactionQuantity(int q) { + requireNonNull(q); + checkArgument(isValidValue(q), MESSAGE_CONSTRAINTS_VALUE); + value = q; + } + + public int getValue() { + return value; + } + + /** + * Returns true if a given string matches the regex of a valid transaction quantity. + */ + public static boolean isValidFormat(String test) { + if (test.matches(VALIDATION_REGEX)) { + try { + int value = Integer.parseInt(test); + return isValidValue(value); + } catch (NumberFormatException e) { + return false; + } + } + return false; + } + + /** + * Returns true if a integer is a valid value for quantity. + */ + public static boolean isValidValue(int test) { + return test >= MIN_VALUE && test <= MAX_VALUE; + } + + /** + * Returns a new quantity whose value is the difference between this value and the + * other's value. + * @param q other quantity. + * @return new quantity. + */ + public Quantity minus(Quantity q) { + requireNonNull(q); + int newValue = value - q.getValue(); + return new TransactionQuantity(newValue); + } + + /** + * Returns a new quantity whose value is the summation between this value and the + * other's value. + * @param q other quantity. + * @return new quantity. + */ + public Quantity plus(Quantity q) { + requireNonNull(q); + int newValue = value + q.getValue(); + return new TransactionQuantity(newValue); + } + + public String toString() { + return String.valueOf(value); + } + + /** + * Returns true if two transaction quantities are equal in value. + */ + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Quantity // instanceof handles nulls + && value == ((Quantity) other).getValue()); // state check + } + + @Override + public int hashCode() { + return Integer.hashCode(value); + } + + @Override + public int compareTo(Quantity q) { + return value - q.getValue(); + } +} diff --git a/src/main/java/seedu/address/model/transaction/UniqueTransactionList.java b/src/main/java/seedu/address/model/transaction/UniqueTransactionList.java new file mode 100644 index 00000000000..461bc1c1826 --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/UniqueTransactionList.java @@ -0,0 +1,114 @@ +package seedu.address.model.transaction; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.transaction.exceptions.DuplicateTransactionException; +import seedu.address.model.transaction.exceptions.TransactionNotFoundException; + + +/** + * Implements a list of non-duplicate transactions + */ +public class UniqueTransactionList implements Iterable { + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent transaction as the given argument. + * @param toCheck the transaction to be checked + * @return true if there is an equivalent transaction in the list. + */ + public boolean contains(Transaction toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameTransaction); + } + + /** + * Adds a transaction to the unique list. + * @param toAdd transaction to be added. + */ + public void add(Transaction toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateTransactionException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent transaction from the list. + * The transaction must exist in the list. + */ + public void remove(Transaction toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new TransactionNotFoundException(); + } + } + + /** + * Replaces the product {@code target} in the list with {@code editedProduct}. + * {@code target} must exist in the list. + * The product identity of {@code editedProduct} must not be the same as another existing product in the list. + */ + public void setTransaction(Transaction target, Transaction editedTransaction) { + requireAllNonNull(target, editedTransaction); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TransactionNotFoundException(); + } + + if (!target.isSameTransaction(editedTransaction) && contains(editedTransaction)) { + throw new DuplicateTransactionException(); + } + + internalList.set(index, editedTransaction); + } + + /** + * Replaces the contents of this list with {@code transactions}. + * {@code transactions} must not contain duplicate products. + */ + public void setTransaction(List transactions) { + requireAllNonNull(transactions); + if (!transactionsAreUnique(transactions)) { + throw new DuplicateTransactionException(); + } + + internalList.setAll(transactions); + } + + /** + * Returns true if {@code transactions} contains only unique products. + */ + private boolean transactionsAreUnique(List transactions) { + for (int i = 0; i < transactions.size() - 1; i++) { + for (int j = i + 1; j < transactions.size(); j++) { + if (transactions.get(i).isSameTransaction(transactions.get(j))) { + return false; + } + } + } + return true; + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return null; + } +} diff --git a/src/main/java/seedu/address/model/transaction/exceptions/DuplicateTransactionException.java b/src/main/java/seedu/address/model/transaction/exceptions/DuplicateTransactionException.java new file mode 100644 index 00000000000..6705dc9d28c --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/exceptions/DuplicateTransactionException.java @@ -0,0 +1,10 @@ +package seedu.address.model.transaction.exceptions; + +/** + * Signals of duplicate transactions + */ +public class DuplicateTransactionException extends RuntimeException { + public DuplicateTransactionException() { + super("Operation would result in duplicate transactions"); + } +} diff --git a/src/main/java/seedu/address/model/transaction/exceptions/TransactionNotFoundException.java b/src/main/java/seedu/address/model/transaction/exceptions/TransactionNotFoundException.java new file mode 100644 index 00000000000..055d1e29b8a --- /dev/null +++ b/src/main/java/seedu/address/model/transaction/exceptions/TransactionNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.transaction.exceptions; + +/** + * Signals that the operation is unable to find the specified transaction. + */ +public class TransactionNotFoundException extends RuntimeException{} diff --git a/src/main/java/seedu/address/model/util/Description.java b/src/main/java/seedu/address/model/util/Description.java new file mode 100644 index 00000000000..1b79823ff6a --- /dev/null +++ b/src/main/java/seedu/address/model/util/Description.java @@ -0,0 +1,58 @@ +package seedu.address.model.util; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Product's description in the product list. + * Guarantees: immutable; is valid as declared in {@link #isValidDescription(String)} + */ +public class Description { + + public static final String MESSAGE_CONSTRAINTS = "Descriptions can take any values, and it should not be blank"; + public static final String DEFAULT_VALUE = "N/A"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code Description}. + * + * @param description A valid description. + */ + public Description(String description) { + requireNonNull(description); + checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS); + value = description; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDescription(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Description // instanceof handles nulls + && value.equals(((Description) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/util/Money.java b/src/main/java/seedu/address/model/util/Money.java new file mode 100644 index 00000000000..3a465c2d265 --- /dev/null +++ b/src/main/java/seedu/address/model/util/Money.java @@ -0,0 +1,107 @@ +package seedu.address.model.util; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Transaction's Monetary Information. + * Guarantees: immutable; is valid as declared in {@link #isValidMoney(String)} + */ +public class Money { + + public static final String MESSAGE_CONSTRAINTS_FORMAT = + "Money should take non-negative integer values (up to 1000000), and it should not be blank"; + public static final String MESSAGE_CONSTRAINTS_VALUE = + "The numeric value of Money must not be negative and must be smaller than 1000000"; + + /* + * There must be one or more digits entered. + */ + public static final String VALIDATION_REGEX = "^\\d{1,7}$"; + + public static final int MAX_VALUE = 1000000; + + public static final int DEFAULT_VALUE = 0; + + public final int value; + + /** + * Constructs an {@code Money}. + * + * @param amount A valid amount in string type. + */ + public Money(String amount) { + requireNonNull(amount); + checkArgument(isValidMoney(amount), MESSAGE_CONSTRAINTS_FORMAT); + int numericValue = Integer.parseInt(amount); + checkArgument(isValidAmount(numericValue), MESSAGE_CONSTRAINTS_VALUE); + value = numericValue; + } + + /** + * Constructs an {@code Money}. + * + * @param amount A valid amount in int type. + */ + public Money(int amount) { + requireNonNull(amount); + checkArgument(isValidAmount(amount), MESSAGE_CONSTRAINTS_VALUE); + value = amount; + } + + /** + * Returns true if a given string is a valid sales. + */ + public static boolean isValidMoney(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns true if the amount is in a valid range. + * @param test integer to be tested. + * @return true if the integer is valid, false otherwise. + */ + public static boolean isValidAmount(int test) { + return test >= 0 && test <= MAX_VALUE; + } + + /** + * Returns a new money whose value is the difference between this value and the + * other's value. + * @param m other money. + * @return new money. + */ + public Money minus(Money m) { + int newValue = value - m.value; + return new Money(newValue); + } + + /** + * Returns a new money whose value is the summation between this value and the + * other's value. + * @param m other money. + * @return new money. + */ + public Money plus(Money m) { + int newValue = value + m.value; + return new Money(newValue); + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Money // instanceof handles nulls + && value == (((Money) other).value)); // state check + } + + @Override + public int hashCode() { + return Integer.hashCode(value); + } + +} diff --git a/src/main/java/seedu/address/model/util/Quantity.java b/src/main/java/seedu/address/model/util/Quantity.java new file mode 100644 index 00000000000..176c8579279 --- /dev/null +++ b/src/main/java/seedu/address/model/util/Quantity.java @@ -0,0 +1,50 @@ +package seedu.address.model.util; + +/** + * The API of quantity. + */ +public interface Quantity extends Comparable { + + String MESSAGE_CONSTRAINTS_FORMAT = + "Quantity should take non-negative integer values (up to 1000000), and it should not be blank"; + + /** + * There must be one or more digits entered. + */ + String VALIDATION_REGEX = "^\\d{1,7}$"; + + int MAX_VALUE = 1000000; + + /** + * Returns the value of the quantity + */ + int getValue(); + + /** + * Returns a new quantity whose value is the difference between this value and the + * other's value. + * @param q other quantity. + * @return new quantity. + */ + Quantity minus(Quantity q); + + /** + * Returns a new quantity whose value is the summation between this value and the + * other's value. + * @param q other quantity. + * @return new quantity. + */ + Quantity plus(Quantity q); + + @Override + String toString(); + + @Override + boolean equals(Object other); + + @Override + int hashCode(); + + @Override + int compareTo(Quantity q); +} diff --git a/src/main/java/seedu/address/model/util/QuantityThreshold.java b/src/main/java/seedu/address/model/util/QuantityThreshold.java new file mode 100644 index 00000000000..77dba428c10 --- /dev/null +++ b/src/main/java/seedu/address/model/util/QuantityThreshold.java @@ -0,0 +1,71 @@ +package seedu.address.model.util; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents the quantity threshold for each product before the application notifies the user. + */ +public class QuantityThreshold { + public static final String MESSAGE_CONSTRAINTS = + "Quantity threshold can take any positive integer values (up to 1000000), and it should not be blank"; + + /* + * There must be one or more digits entered. + */ + public static final String VALIDATION_REGEX = "\\d+"; + + public static final int MAX_VALUE = 1000000; + + public final int value; + + /** + * Constructs an {@code QuantityThreshold}. + * + * @param quantityThreshold A valid quantityThreshold. + */ + public QuantityThreshold(String quantityThreshold) { + requireNonNull(quantityThreshold); + checkArgument(isValidQuantity(quantityThreshold), MESSAGE_CONSTRAINTS); + value = Integer.parseInt(quantityThreshold); + } + + public QuantityThreshold(int quantityThreshold) { + requireNonNull(quantityThreshold); + checkArgument(isValidQuantityValue(quantityThreshold), MESSAGE_CONSTRAINTS); + value = quantityThreshold; + } + + /** + * Returns true if a given string is a valid quantity threshold. + */ + public static boolean isValidQuantity(String test) { + return test.matches(VALIDATION_REGEX) + && Integer.parseInt(test) < MAX_VALUE; + } + + public static boolean isValidQuantityValue(int test) { + return test > 0 && test < MAX_VALUE; + } + + public double getDouble() { + return Double.valueOf(value); + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof QuantityThreshold // instanceof handles nulls + && value == ((QuantityThreshold) other).value); // state check + } + + @Override + public int hashCode() { + return Integer.hashCode(value); + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..c4233d8c395 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -4,46 +4,131 @@ import java.util.Set; import java.util.stream.Collectors; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.InventorySystem; +import seedu.address.model.ReadOnlyInventorySystem; +import seedu.address.model.customer.Address; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; +import seedu.address.model.product.CostPrice; +import seedu.address.model.product.Price; +import seedu.address.model.product.Product; +import seedu.address.model.product.ProductQuantity; import seedu.address.model.tag.Tag; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.Transaction; /** - * Contains utility methods for populating {@code AddressBook} with sample data. + * Contains utility methods for populating {@code InventorySystem} with sample data. */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + + public static final Customer ALEX_YEOH = new Customer(new Name("Alex Yeoh"), new Phone("87438807"), + new Email("alexyeoh@example.com"), new Address("Blk 30 Geylang Street 29, #06-40"), + getTagSet("friends")); + + public static final Customer BERNICE_YU = new Customer(new Name("Bernice Yu"), new Phone("99272758"), + new Email("berniceyu@example.com"), new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + getTagSet("colleagues", "friends")); + + public static final Customer CHARLOTTE = new Customer(new Name("Charlotte Oliveiro"), new Phone("93210283"), + new Email("charlotte@example.com"), new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + getTagSet("neighbours")); + + public static final Customer DAVID_LI = new Customer(new Name("David Li"), new Phone("91031282"), + new Email("lidavid@example.com"), new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + getTagSet("family")); + + public static final Customer IRFAN = new Customer(new Name("Irfan Ibrahim"), new Phone("92492021"), + new Email("irfan@example.com"), new Address("Blk 47 Tampines Street 20, #17-35"), + getTagSet("classmates")); + + public static final Customer ROY = new Customer(new Name("Roy Balakrishnan"), new Phone("92624417"), + new Email("royb@example.com"), new Address("Blk 45 Aljunied Street 85, #11-31"), + getTagSet("colleagues")); + + public static final Customer VIVIAN_TAN = new Customer(new Name("Vivian Tan"), new Phone("86724359"), + new Email("viviantan@example.com"), new Address("Blk 239 Tampines Street 31, #08-21"), + getTagSet("classmates", "colleagues")); + + public static final Customer MARCUS_NEO = new Customer(new Name("Marcus Neo"), new Phone("95482145"), + new Email("marcusneo@mail.com"), new Address("Blk 834 Bukit Panjang Street 19, #12-08"), + getTagSet("celebrity")); + + public static final Product IPAD_PRO = new Product(new Description("iPad Pro"), new CostPrice("599"), + new Price("1299"), new ProductQuantity(29), new Money(14289), + new QuantityThreshold("8"), 0.725); + + public static final Product AIRPODS_PRO = new Product(new Description("Airpods Pro"), new CostPrice("129"), + new Price("379"), new ProductQuantity(96), new Money(1516), + new QuantityThreshold("20"), 0.96); + + public static final Product IPHONE_X = new Product(new Description("iPhone X"), new CostPrice("349"), + new Price("1199"), new ProductQuantity(60), new Money(Money.DEFAULT_VALUE), + new QuantityThreshold("12"), 1); + + public static final Product IPHONE_8PLUS = new Product(new Description("iPhone 8 Plus"), new CostPrice("299"), + new Price("950"), new ProductQuantity(48), new Money(1900), + new QuantityThreshold("10"), 0.96); + + public static final Product MACBOOK_AIR = new Product(new Description("Macbook Air"), new CostPrice("680"), + new Price("1449"), new ProductQuantity(29), new Money(1499), + new QuantityThreshold("6"), 0.967); + + public static final Product APPLE_WATCH = new Product(new Description("Apple Watch"), new CostPrice("109"), + new Price("440"), new ProductQuantity(80), new Money(Money.DEFAULT_VALUE), + new QuantityThreshold("16"), 1); + + public static final Product APPLE_PENCIL = new Product(new Description("Apple Pencil"), new CostPrice("59"), + new Price("189"), new ProductQuantity(100), new Money(Money.DEFAULT_VALUE), + new QuantityThreshold("20"), 1); + + public static Customer[] getSamplePersons() { + return new Customer[] { + ALEX_YEOH, BERNICE_YU, CHARLOTTE, DAVID_LI, IRFAN, VIVIAN_TAN, MARCUS_NEO + }; + } + + public static Product[] getSampleProducts() { + return new Product[] { + IPAD_PRO, AIRPODS_PRO, IPHONE_X, IPHONE_8PLUS, MACBOOK_AIR, APPLE_WATCH, APPLE_PENCIL + }; + } + + public static Transaction[] getSampleTransactions() { + return new Transaction[] { + new Transaction(ALEX_YEOH, IPAD_PRO, ALEX_YEOH.getId(), IPAD_PRO.getId(), + new DateTime(DateTime.DEFAULT_VALUE), new ProductQuantity("2"), new Money("2598"), + new Description("N/A")), + new Transaction(BERNICE_YU, IPHONE_8PLUS, BERNICE_YU.getId(), IPHONE_8PLUS.getId(), + new DateTime(DateTime.DEFAULT_VALUE), new ProductQuantity("2"), new Money("1900"), + new Description("N/A")), + new Transaction(DAVID_LI, IPAD_PRO, DAVID_LI.getId(), IPAD_PRO.getId(), + new DateTime(DateTime.DEFAULT_VALUE), new ProductQuantity("4"), new Money("5196"), + new Description("N/A")), + new Transaction(VIVIAN_TAN, MACBOOK_AIR, VIVIAN_TAN.getId(), MACBOOK_AIR.getId(), + new DateTime(DateTime.DEFAULT_VALUE), new ProductQuantity("1"), new Money("1449"), + new Description("N/A")), + new Transaction(IRFAN, AIRPODS_PRO, IRFAN.getId(), AIRPODS_PRO.getId(), + new DateTime(DateTime.DEFAULT_VALUE), new ProductQuantity("4"), new Money("1516"), + new Description("N/A")), + new Transaction(CHARLOTTE, IPAD_PRO, CHARLOTTE.getId(), IPAD_PRO.getId(), + new DateTime(DateTime.DEFAULT_VALUE), new ProductQuantity("5"), new Money("6495"), + new Description("N/A")) }; } - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + public static ReadOnlyInventorySystem getSampleInventorySystem() { + InventorySystem sampleAb = new InventorySystem(); + for (Customer sampleCustomer : getSamplePersons()) { + sampleAb.addPerson(sampleCustomer); + } + for (Product sampleProduct : getSampleProducts()) { + sampleAb.addProduct(sampleProduct); + } + for (Transaction sampleTransaction : getSampleTransactions()) { + sampleAb.addTransaction(sampleTransaction); } return sampleAb; } diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f9..00000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/InventorySystemStorage.java b/src/main/java/seedu/address/storage/InventorySystemStorage.java new file mode 100644 index 00000000000..59376e3f9dc --- /dev/null +++ b/src/main/java/seedu/address/storage/InventorySystemStorage.java @@ -0,0 +1,46 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.InventorySystem; +import seedu.address.model.ReadOnlyInventorySystem; + +/** + * Represents a storage for {@link InventorySystem}. + */ +public interface InventorySystemStorage { + + /** + * Returns the file path of the data file. + */ + Path getInventorySystemFilePath(); + + /** + * Returns InventorySystem data as a {@link ReadOnlyInventorySystem}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readInventorySystem() throws DataConversionException, IOException; + + /** + * @see #getInventorySystemFilePath() + */ + Optional readInventorySystem(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyInventorySystem} to the storage. + * @param inventorySystem cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveInventorySystem(ReadOnlyInventorySystem inventorySystem) throws IOException; + + /** + * @see #saveInventorySystem(ReadOnlyInventorySystem) + */ + void saveInventorySystem(ReadOnlyInventorySystem addressBook, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2ea..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tagged = new ArrayList<>(); - - /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. - */ - @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson use. - */ - public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java index 0df22bdb754..541d6b57dd1 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java @@ -9,7 +9,7 @@ /** * Jackson-friendly version of {@link Tag}. */ -class JsonAdaptedTag { +public class JsonAdaptedTag { private final String tagName; diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonInventorySystemStorage.java similarity index 52% rename from src/main/java/seedu/address/storage/JsonAddressBookStorage.java rename to src/main/java/seedu/address/storage/JsonInventorySystemStorage.java index dfab9daaa0d..353d5b92e15 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/JsonInventorySystemStorage.java @@ -12,41 +12,41 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.FileUtil; import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyInventorySystem; /** - * A class to access AddressBook data stored as a json file on the hard disk. + * A class to access InventorySystem data stored as a json file on the hard disk. */ -public class JsonAddressBookStorage implements AddressBookStorage { +public class JsonInventorySystemStorage implements InventorySystemStorage { - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); + private static final Logger logger = LogsCenter.getLogger(JsonInventorySystemStorage.class); private Path filePath; - public JsonAddressBookStorage(Path filePath) { + public JsonInventorySystemStorage(Path filePath) { this.filePath = filePath; } - public Path getAddressBookFilePath() { + public Path getInventorySystemFilePath() { return filePath; } @Override - public Optional readAddressBook() throws DataConversionException { - return readAddressBook(filePath); + public Optional readInventorySystem() throws DataConversionException { + return readInventorySystem(filePath); } /** - * Similar to {@link #readAddressBook()}. + * Similar to {@link #readInventorySystem()}. * * @param filePath location of the data. Cannot be null. * @throws DataConversionException if the file is not in the correct format. */ - public Optional readAddressBook(Path filePath) throws DataConversionException { + public Optional readInventorySystem(Path filePath) throws DataConversionException { requireNonNull(filePath); - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); + Optional jsonAddressBook = JsonUtil.readJsonFile( + filePath, JsonSerializableInventorySystem.class); if (!jsonAddressBook.isPresent()) { return Optional.empty(); } @@ -60,21 +60,21 @@ public Optional readAddressBook(Path filePath) throws DataC } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); + public void saveInventorySystem(ReadOnlyInventorySystem inventorySystem) throws IOException { + saveInventorySystem(inventorySystem, filePath); } /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. + * Similar to {@link #saveInventorySystem(ReadOnlyInventorySystem)}. * * @param filePath location of the data. Cannot be null. */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); + public void saveInventorySystem(ReadOnlyInventorySystem inventorySystem, Path filePath) throws IOException { + requireNonNull(inventorySystem); requireNonNull(filePath); FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); + JsonUtil.saveJsonFile(new JsonSerializableInventorySystem(inventorySystem), filePath); } } diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java deleted file mode 100644 index 5efd834091d..00000000000 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonRootName; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to JSON format. - */ -@JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - private final List persons = new ArrayList<>(); - - /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. - */ - @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); - } - - /** - * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. - * - * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. - */ - public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this address book into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - -} diff --git a/src/main/java/seedu/address/storage/JsonSerializableInventorySystem.java b/src/main/java/seedu/address/storage/JsonSerializableInventorySystem.java new file mode 100644 index 00000000000..714f7007535 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonSerializableInventorySystem.java @@ -0,0 +1,94 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.InventorySystem; +import seedu.address.model.ReadOnlyInventorySystem; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; +import seedu.address.storage.customer.JsonAdaptedCustomer; +import seedu.address.storage.product.JsonAdaptedProduct; +import seedu.address.storage.transaction.JsonAdaptedTransaction; + +/** + * An Immutable InventorySystem that is serializable to JSON format. + */ +@JsonRootName(value = "addressbook") +class JsonSerializableInventorySystem { + + public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate customer(s)."; + public static final String MESSAGE_DUPLICATE_TRANSACTION = "Transactions list contains duplicate transaction(s)."; + public static final String MESSAGE_DUPLICATE_PRODUCT = "Products list contains duplicate product(s)."; + + private final List persons = new ArrayList<>(); + private final List transactions = new ArrayList<>(); + private final List products = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableInventorySystem} with the given persons, transactions and products. + */ + @JsonCreator + public JsonSerializableInventorySystem(@JsonProperty("persons") List persons, + @JsonProperty("transactions") List transactions, + @JsonProperty("products") List products) { + this.persons.addAll(persons); + this.transactions.addAll(transactions); + this.products.addAll(products); + } + + /** + * Converts a given {@code ReadOnlyInventorySystem} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableInventorySystem}. + */ + public JsonSerializableInventorySystem(ReadOnlyInventorySystem source) { + persons.addAll(source.getPersonList().stream().map(JsonAdaptedCustomer::new).collect(Collectors.toList())); + transactions.addAll(source.getTransactionList().stream().map(JsonAdaptedTransaction::new) + .collect(Collectors.toList())); + products.addAll(source.getProductList().stream().map(JsonAdaptedProduct::new) + .collect(Collectors.toList())); + } + + /** + * Converts this address book into the model's {@code InventorySystem} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public InventorySystem toModelType() throws IllegalValueException { + InventorySystem addressBook = new InventorySystem(); + for (JsonAdaptedCustomer jsonAdaptedCustomer : persons) { + Customer customer = jsonAdaptedCustomer.toModelType(); + if (addressBook.hasPerson(customer)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); + } + addressBook.addPerson(customer); + } + + for (JsonAdaptedTransaction jsonAdaptedTransaction : transactions) { + Transaction transaction = jsonAdaptedTransaction.toModelType(); + if (addressBook.hasTransaction(transaction)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_TRANSACTION); + } + addressBook.addTransaction(transaction); + } + + for (JsonAdaptedProduct jsonAdaptedProduct : products) { + Product product = jsonAdaptedProduct.toModelType(); + if (addressBook.hasProduct(product)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_PRODUCT); + } + addressBook.addProduct(product); + } + + return addressBook; + } + +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index beda8bd9f11..bbcf7eaa090 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -5,14 +5,14 @@ import java.util.Optional; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyInventorySystem; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends InventorySystemStorage, UserPrefsStorage { @Override Optional readUserPrefs() throws DataConversionException, IOException; @@ -21,12 +21,12 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; @Override - Path getAddressBookFilePath(); + Path getInventorySystemFilePath(); @Override - Optional readAddressBook() throws DataConversionException, IOException; + Optional readInventorySystem() throws DataConversionException, IOException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveInventorySystem(ReadOnlyInventorySystem inventorySystem) throws IOException; } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index e4f452b6cbf..6bd1c96c022 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -7,21 +7,21 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyInventorySystem; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; /** - * Manages storage of AddressBook data in local storage. + * Manages storage of InventorySystem data in local storage. */ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; + private InventorySystemStorage addressBookStorage; private UserPrefsStorage userPrefsStorage; - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(InventorySystemStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { super(); this.addressBookStorage = addressBookStorage; this.userPrefsStorage = userPrefsStorage; @@ -45,33 +45,34 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { } - // ================ AddressBook methods ============================== + // ================ InventorySystem methods ============================== @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); + public Path getInventorySystemFilePath() { + return addressBookStorage.getInventorySystemFilePath(); } @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); + public Optional readInventorySystem() throws DataConversionException, IOException { + return readInventorySystem(addressBookStorage.getInventorySystemFilePath()); } @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { + public Optional readInventorySystem(Path filePath) throws DataConversionException, + IOException { logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); + return addressBookStorage.readInventorySystem(filePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); + public void saveInventorySystem(ReadOnlyInventorySystem inventorySystem) throws IOException { + saveInventorySystem(inventorySystem, addressBookStorage.getInventorySystemFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveInventorySystem(ReadOnlyInventorySystem addressBook, Path filePath) throws IOException { logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); + addressBookStorage.saveInventorySystem(addressBook, filePath); } } diff --git a/src/main/java/seedu/address/storage/customer/JsonAdaptedCustomer.java b/src/main/java/seedu/address/storage/customer/JsonAdaptedCustomer.java new file mode 100644 index 00000000000..be8830cfdae --- /dev/null +++ b/src/main/java/seedu/address/storage/customer/JsonAdaptedCustomer.java @@ -0,0 +1,138 @@ +package seedu.address.storage.customer; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.customer.Address; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; +import seedu.address.model.tag.Tag; +import seedu.address.storage.JsonAdaptedTag; + +/** + * Jackson-friendly version of {@link Customer}. + */ +public class JsonAdaptedCustomer { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Customer's %s field is missing!"; + + private final String id; + private final String name; + private final String phone; + private final String email; + private final String address; + private final List tagged = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedCustomer} with the given customer details. + */ + @JsonCreator + public JsonAdaptedCustomer(@JsonProperty("id") String id, @JsonProperty("name") String name, + @JsonProperty("phone") String phone, @JsonProperty("email") String email, + @JsonProperty("address") String address, + @JsonProperty("tagged") List tagged) { + this.id = id; + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + if (tagged != null) { + this.tagged.addAll(tagged); + } + } + + /** + * Converts a given {@code Customer} into this class for Jackson use. + */ + public JsonAdaptedCustomer(Customer source) { + id = source.getId().toString(); + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + tagged.addAll(source.getTags().stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted customer object into the model's {@code Customer} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted customer. + */ + public Customer toModelType() throws IllegalValueException { + final UUID modelUuid = getUuid(); + final Name modelName = getName(); + final Phone modelPhone = getPhone(); + final Email modelEmail = getEmail(); + final Address modelAddress = getAddress(); + final Set modelTags = getTags(); + + return new Customer(modelUuid, modelName, modelPhone, modelEmail, modelAddress, modelTags); + } + + private UUID getUuid() throws IllegalValueException { + if (id == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, String.class.getSimpleName())); + } + return UUID.fromString(id); + } + + private Name getName() throws IllegalValueException { + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + return new Name(name); + } + + private Phone getPhone() throws IllegalValueException { + if (phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(phone)) { + throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); + } + return new Phone(phone); + } + + private Email getEmail() throws IllegalValueException { + if (email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(email)) { + throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); + } + return new Email(email); + } + + private Address getAddress() throws IllegalValueException { + if (address == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + } + if (!Address.isValidAddress(address)) { + throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + } + return new Address(address); + } + + private HashSet getTags() throws IllegalValueException { + final List personTags = new ArrayList<>(); + for (JsonAdaptedTag tag : tagged) { + personTags.add(tag.toModelType()); + } + return new HashSet<>(personTags); + } +} diff --git a/src/main/java/seedu/address/storage/product/JsonAdaptedProduct.java b/src/main/java/seedu/address/storage/product/JsonAdaptedProduct.java new file mode 100644 index 00000000000..57714097e31 --- /dev/null +++ b/src/main/java/seedu/address/storage/product/JsonAdaptedProduct.java @@ -0,0 +1,157 @@ +package seedu.address.storage.product; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.product.CostPrice; +import seedu.address.model.product.Price; +import seedu.address.model.product.Product; +import seedu.address.model.product.ProductQuantity; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; +import seedu.address.model.util.QuantityThreshold; + +/** + * Jackson-friendly version of {@link Product}. + */ +public class JsonAdaptedProduct { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Product's %s field is missing!"; + + private final String description; + private final String costPrice; + private final String price; + private final String quantity; + private final String money; + private final String id; + private final String threshold; + + /** + * Constructs a {@code JsonAdaptedProduct} with the given product details. + */ + @JsonCreator + public JsonAdaptedProduct(@JsonProperty("description") String description, + @JsonProperty("costPrice") String costPrice, @JsonProperty("price") String price, + @JsonProperty("quantity") String quantity, @JsonProperty("money") String money, + @JsonProperty("threshold") String threshold, @JsonProperty("id") String id) { + this.description = description; + this.costPrice = costPrice; + this.price = price; + this.quantity = quantity; + this.money = money; + this.threshold = threshold; + this.id = id; + } + + /** + * Converts a given {@code Product} into this class for Jackson use. + */ + public JsonAdaptedProduct(Product source) { + description = source.getDescription().value; + costPrice = source.getCostPrice().value; + price = source.getPrice().value; + quantity = source.getQuantity().toString(); + money = source.getMoney().toString(); + threshold = source.getThreshold().toString(); + id = source.getId().toString(); + } + + /** + * Converts this Jackson-friendly adapted person object into the model's {@code Product} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted product. + */ + public Product toModelType() throws IllegalValueException { + final UUID modelId = getUuid(); + final Description modelDescription = getDescription(); + final CostPrice modelCostPrice = getCostPrice(); + final Price modelPrice = getPrice(); + final Quantity modelQuantity = getQuantity(); + final Money modelMoney = getMoney(); + final QuantityThreshold modelQuantityThreshold = getQuantityThreshold(); + + return new Product(modelId, modelDescription, modelCostPrice, modelPrice, modelQuantity, + modelMoney, modelQuantityThreshold, 1); + } + + private UUID getUuid() throws IllegalValueException { + if (id == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, String.class.getSimpleName())); + } + return UUID.fromString(id); + } + + private Description getDescription() throws IllegalValueException { + if (description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Description.class.getSimpleName())); + } + if (!Description.isValidDescription(description)) { + throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS); + } + return new Description(description); + } + + private CostPrice getCostPrice() throws IllegalValueException { + if (costPrice == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + CostPrice.class.getSimpleName())); + } + if (!CostPrice.isValidPrice(costPrice)) { + throw new IllegalValueException(CostPrice.MESSAGE_CONSTRAINTS); + } + return new CostPrice(costPrice); + } + + private Price getPrice() throws IllegalValueException { + if (price == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Price.class.getSimpleName())); + } + if (!Price.isValidPrice(price)) { + throw new IllegalValueException(Price.MESSAGE_CONSTRAINTS); + } + return new Price(price); + } + + private Quantity getQuantity() throws IllegalValueException { + if (quantity == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Quantity.class.getSimpleName())); + } + if (!ProductQuantity.isValidFormat(quantity)) { + throw new IllegalValueException(ProductQuantity.MESSAGE_CONSTRAINTS_FORMAT); + } + if (!ProductQuantity.isValidValue(Integer.parseInt(quantity))) { + throw new IllegalValueException(ProductQuantity.MESSAGE_CONSTRAINTS_VALUE); + } + return new ProductQuantity(quantity); + } + + private Money getMoney() throws IllegalValueException { + if (money == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Money.class.getSimpleName())); + } + if (!Money.isValidMoney(money)) { + throw new IllegalValueException(Money.MESSAGE_CONSTRAINTS_FORMAT); + } + if (!Money.isValidAmount(Integer.parseInt(money))) { + throw new IllegalValueException(Money.MESSAGE_CONSTRAINTS_VALUE); + } + return new Money(money); + } + + private QuantityThreshold getQuantityThreshold() throws IllegalValueException { + if (threshold == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + QuantityThreshold.class.getSimpleName())); + } + if (!QuantityThreshold.isValidQuantity(threshold)) { + throw new IllegalValueException(QuantityThreshold.MESSAGE_CONSTRAINTS); + } + return new QuantityThreshold(threshold); + } +} diff --git a/src/main/java/seedu/address/storage/transaction/JsonAdaptedTransaction.java b/src/main/java/seedu/address/storage/transaction/JsonAdaptedTransaction.java new file mode 100644 index 00000000000..f43588113a8 --- /dev/null +++ b/src/main/java/seedu/address/storage/transaction/JsonAdaptedTransaction.java @@ -0,0 +1,166 @@ +package seedu.address.storage.transaction; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.transaction.TransactionQuantity; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; +import seedu.address.storage.customer.JsonAdaptedCustomer; +import seedu.address.storage.product.JsonAdaptedProduct; + +/** + * Jackson-friendly version of {@link Transaction}. + */ +public class JsonAdaptedTransaction { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Transaction's %s field is missing!"; + + private final JsonAdaptedCustomer customer; + private final JsonAdaptedProduct product; + private final String productId; + private final String customerId; + private final String dateTime; + private final String quantity; + private final String money; + private final String description; + + /** + * Constructs a {@code JsonAdaptedCustomer} with the given customer details. + */ + @JsonCreator + public JsonAdaptedTransaction(@JsonProperty("customer") JsonAdaptedCustomer customer, + @JsonProperty("product") JsonAdaptedProduct product, + @JsonProperty("customerId") String customerId, + @JsonProperty("productId") String productId, + @JsonProperty("dateTime") String dateTime, @JsonProperty("quantity") String quantity, + @JsonProperty("money") String money, @JsonProperty("description") String description) { + this.customer = customer; + this.product = product; + this.customerId = customerId; + this.productId = productId; + this.dateTime = dateTime; + this.quantity = quantity; + this.money = money; + this.description = description; + } + + /** + * Converts a given {@code Transaction} into this class for Jackson use. + */ + public JsonAdaptedTransaction(Transaction source) { + customer = new JsonAdaptedCustomer(source.getCustomer()); + product = new JsonAdaptedProduct(source.getProduct()); + customerId = source.getCustomerId().toString(); + productId = source.getProductId().toString(); + dateTime = source.getDateTime().toString(); + quantity = source.getQuantity().toString(); + money = source.getMoney().toString(); + description = source.getDescription().toString(); + } + + /** + * Converts this Jackson-friendly adapted customer object into the model's {@code Customer} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted customer. + */ + public Transaction toModelType() throws IllegalValueException { + final Customer modelCustomer = getCustomer(); + final Product modelProduct = getProduct(); + final UUID modelCustomerId = getCustomerId(); + final UUID modelProductId = getProductId(); + final DateTime modelDateTime = getDateTime(); + final Quantity modelQuantity = getQuantity(); + final Money modelMoney = getMoney(); + final Description modelDescription = getDescription(); + + return new Transaction(modelCustomer, modelProduct, modelCustomerId, modelProductId, + modelDateTime, modelQuantity, modelMoney, modelDescription); + } + + private Customer getCustomer() throws IllegalValueException { + if (customer == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, String.class.getSimpleName())); + } + return customer.toModelType(); + } + + private Product getProduct() throws IllegalValueException { + if (product == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, String.class.getSimpleName())); + } + return product.toModelType(); + } + + private UUID getCustomerId() throws IllegalValueException { + if (customerId == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, String.class.getSimpleName())); + } + return UUID.fromString(customerId); + } + + private UUID getProductId() throws IllegalValueException { + if (productId == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, String.class.getSimpleName())); + } + return UUID.fromString(productId); + } + + private DateTime getDateTime() throws IllegalValueException { + if (dateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DateTime.class.getSimpleName())); + } + if (!DateTime.isValidDateTime(dateTime)) { + throw new IllegalValueException(DateTime.MESSAGE_CONSTRAINTS); + } + return new DateTime(dateTime); + } + + private Quantity getQuantity() throws IllegalValueException { + if (quantity == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Quantity.class.getSimpleName())); + } + if (!TransactionQuantity.isValidFormat(quantity)) { + throw new IllegalValueException(Quantity.MESSAGE_CONSTRAINTS_FORMAT); + } + if (!TransactionQuantity.isValidValue(Integer.parseInt(quantity))) { + throw new IllegalValueException(TransactionQuantity.MESSAGE_CONSTRAINTS_VALUE); + } + return new TransactionQuantity(quantity); + } + + private Money getMoney() throws IllegalValueException { + if (money == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Money.class.getSimpleName())); + } + if (!Money.isValidMoney(money)) { + throw new IllegalValueException(Money.MESSAGE_CONSTRAINTS_FORMAT); + } + if (!Money.isValidAmount(Integer.parseInt(money))) { + throw new IllegalValueException(Money.MESSAGE_CONSTRAINTS_VALUE); + } + return new Money(money); + } + + private Description getDescription() throws IllegalValueException { + if (description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Description.class.getSimpleName())); + } + if (!Description.isValidDescription(description)) { + throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS); + } + return new Description(description); + } +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 7d76e691f52..0173486c354 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -1,8 +1,12 @@ package seedu.address.ui; +import java.util.LinkedList; +import java.util.ListIterator; + import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; @@ -15,9 +19,13 @@ public class CommandBox extends UiPart { public static final String ERROR_STYLE_CLASS = "error"; private static final String FXML = "CommandBox.fxml"; + private static final int MAX_BUFFER_SIZE = 100; // size of buffer to store command history private final CommandExecutor commandExecutor; + private LinkedList commandHistory = new LinkedList<>(); + private ListIterator historyIterator = commandHistory.listIterator(0); + @FXML private TextField commandTextField; @@ -26,6 +34,41 @@ public CommandBox(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); + commandTextField.setOnKeyPressed(event -> keyPressedEvent(event)); + } + + /** + * Event handler for text field when an key is pressed. + * @param event KeyEvent to process. + */ + private void keyPressedEvent(KeyEvent event) { + switch (event.getCode()) { + case UP: + traverseHistory(-1); + break; + case DOWN: + traverseHistory(1); + break; + default: + break; + } + } + + /** + * Goes through the text field history. + * @param index positive to traverse down, negative to traverse up. + */ + private void traverseHistory(int index) { + String command = ""; + if (index > 0 && historyIterator.hasNext()) { + command = historyIterator.next(); + } else if (index < 0 && historyIterator.hasPrevious()) { + command = historyIterator.previous(); + } else { + command = commandTextField.getText(); + } + commandTextField.setText(command); + commandTextField.positionCaret(command.length()); // position the text cursor at the end of the command } /** @@ -34,7 +77,13 @@ public CommandBox(CommandExecutor commandExecutor) { @FXML private void handleCommandEntered() { try { - commandExecutor.execute(commandTextField.getText()); + String command = commandTextField.getText(); + if (commandHistory.size() >= MAX_BUFFER_SIZE) { + commandHistory.pop(); + } + commandHistory.offer(command); + historyIterator = commandHistory.listIterator(commandHistory.size()); + commandExecutor.execute(command); commandTextField.setText(""); } catch (CommandException | ParseException e) { setStyleToIndicateCommandFailure(); diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 9a665915949..7777c347522 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -1,12 +1,11 @@ package seedu.address.ui; +import java.net.URISyntaxException; import java.util.logging.Logger; import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; +import javafx.scene.media.MediaException; +import javafx.scene.web.WebView; import javafx.stage.Stage; import seedu.address.commons.core.LogsCenter; @@ -15,17 +14,13 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; + public static final String USERGUIDE_URL = "https://ay1920s2-cs2103-t09-2.github.io/main/UserGuide.html"; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; @FXML - private Button copyButton; - - @FXML - private Label helpMessage; + private WebView webView; /** * Creates a new HelpWindow. @@ -34,7 +29,13 @@ public class HelpWindow extends UiPart { */ public HelpWindow(Stage root) { super(FXML, root); - helpMessage.setText(HELP_MESSAGE); + + try { + String uri = HelpWindow.class.getResource("/html/userGuide.html").toURI().toString(); + webView.getEngine().load(uri); + } catch (URISyntaxException | MediaException ex) { + ex.printStackTrace(); + } } /** @@ -66,6 +67,7 @@ public void show() { logger.fine("Showing help page about the application."); getRoot().show(); getRoot().centerOnScreen(); + webView.getEngine().reload(); } /** @@ -75,13 +77,6 @@ public boolean isShowing() { return getRoot().isShowing(); } - /** - * Hides the help window. - */ - public void hide() { - getRoot().hide(); - } - /** * Focuses on the help window. */ @@ -89,14 +84,4 @@ public void focus() { getRoot().requestFocus(); } - /** - * Copies the URL to the user guide to the clipboard. - */ - @FXML - private void copyUrl() { - final Clipboard clipboard = Clipboard.getSystemClipboard(); - final ClipboardContent url = new ClipboardContent(); - url.putString(USERGUIDE_URL); - clipboard.setContent(url); - } } diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 90bbf11de97..880d5e40842 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -2,13 +2,15 @@ import java.util.logging.Logger; -import javafx.event.ActionEvent; +import javafx.application.Platform; import javafx.fxml.FXML; -import javafx.scene.control.MenuItem; -import javafx.scene.control.TextInputControl; -import javafx.scene.input.KeyCombination; -import javafx.scene.input.KeyEvent; +import javafx.geometry.Pos; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; import javafx.stage.Stage; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; @@ -16,6 +18,11 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.product.Product; +import seedu.address.ui.customer.CustomerListPanel; +import seedu.address.ui.product.ProductListPanel; +import seedu.address.ui.statistics.StatisticsListPanel; +import seedu.address.ui.transaction.TransactionListPanel; /** * The Main Window. Provides the basic application layout containing @@ -31,18 +38,30 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private CustomerListPanel personListPanel; + private ProductListPanel productListPanel; + private TransactionListPanel transactionListPanel; + private StatisticsListPanel statisticsListPanel; + private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private NotificationWindow notificationWindow; + private PlotWindow plotWindow; @FXML private StackPane commandBoxPlaceholder; @FXML - private MenuItem helpMenuItem; + private StackPane customerListPanelPlaceholder; + + @FXML + private StackPane productListPanelPlaceholder; @FXML - private StackPane personListPanelPlaceholder; + private StackPane transactionListPanelPlaceholder; + + @FXML + private StackPane statisticsPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -60,64 +79,51 @@ public MainWindow(Stage primaryStage, Logic logic) { // Configure the UI setWindowDefaultSize(logic.getGuiSettings()); - setAccelerators(); - helpWindow = new HelpWindow(); + notificationWindow = new NotificationWindow(); + plotWindow = new PlotWindow(); } public Stage getPrimaryStage() { return primaryStage; } - private void setAccelerators() { - setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); - } - - /** - * Sets the accelerator of a MenuItem. - * @param keyCombination the KeyCombination value of the accelerator - */ - private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { - menuItem.setAccelerator(keyCombination); - - /* - * TODO: the code below can be removed once the bug reported here - * https://bugs.openjdk.java.net/browse/JDK-8131666 - * is fixed in later version of SDK. - * - * According to the bug report, TextInputControl (TextField, TextArea) will - * consume function-key events. Because CommandBox contains a TextField, and - * ResultDisplay contains a TextArea, thus some accelerators (e.g F1) will - * not work when the focus is in them because the key event is consumed by - * the TextInputControl(s). - * - * For now, we add following event filter to capture such key events and open - * help window purposely so to support accelerators even when focus is - * in CommandBox or ResultDisplay. - */ - getRoot().addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getTarget() instanceof TextInputControl && keyCombination.match(event)) { - menuItem.getOnAction().handle(new ActionEvent()); - event.consume(); - } - }); - } /** * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + personListPanel = new CustomerListPanel(logic.getFilteredCustomerList()); + customerListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + + productListPanel = new ProductListPanel(logic.getFilteredProductList()); + productListPanelPlaceholder.getChildren().add(productListPanel.getRoot()); + + transactionListPanel = new TransactionListPanel(logic.getFilteredTransactionList()); + transactionListPanelPlaceholder.getChildren().add(transactionListPanel.getRoot()); + + statisticsListPanel = new StatisticsListPanel(logic); + statisticsPanelPlaceholder.getChildren().add(statisticsListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getInventorySystemFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); - commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + Label commandBoxLabel = new Label("Enter command"); + commandBoxLabel.setTextFill(Color.web("#ffffff")); + + HBox commandHBox = new HBox(); + HBox.setHgrow(commandBoxLabel, Priority.ALWAYS); + HBox.setHgrow(commandBox.getRoot(), Priority.ALWAYS); + commandHBox.setAlignment(Pos.BASELINE_CENTER); + commandHBox.setSpacing(10); + commandHBox.getChildren().addAll(commandBoxLabel, commandBox.getRoot()); + + commandBoxPlaceholder.getChildren().add(commandHBox); } /** @@ -144,6 +150,31 @@ public void handleHelp() { } } + /** + * Plot the sales of a product. + */ + @FXML + public void handlePlot(XYChart.Series dataSeries, String title) { + if (!plotWindow.isShowing()) { + plotWindow.show(dataSeries, title); + } else { + plotWindow.focus(); + } + } + + /** + * + */ + @FXML + public void handleNotification(Product editedProduct) { + if (!notificationWindow.isShowing()) { + notificationWindow.show(editedProduct.getDescription(), editedProduct.getQuantity()); + } else { + notificationWindow.focus(); + } + } + + void show() { primaryStage.show(); } @@ -156,12 +187,8 @@ private void handleExit() { GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), (int) primaryStage.getX(), (int) primaryStage.getY()); logic.setGuiSettings(guiSettings); - helpWindow.hide(); - primaryStage.hide(); - } - - public PersonListPanel getPersonListPanel() { - return personListPanel; + Platform.exit(); + System.exit(0); } /** @@ -175,6 +202,17 @@ private CommandResult executeCommand(String commandText) throws CommandException logger.info("Result: " + commandResult.getFeedbackToUser()); resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + statisticsPanelPlaceholder.getChildren().removeAll(); + statisticsPanelPlaceholder.getChildren().add(new StatisticsListPanel(logic).getRoot()); + + if (commandResult.isShowNotification()) { + handleNotification(commandResult.getNotificationData()); + } + + if (commandResult.isShowPlot()) { + handlePlot(commandResult.getDataSeries(), commandResult.getTitle()); + } + if (commandResult.isShowHelp()) { handleHelp(); } diff --git a/src/main/java/seedu/address/ui/NotificationWindow.java b/src/main/java/seedu/address/ui/NotificationWindow.java new file mode 100644 index 00000000000..ba751ada386 --- /dev/null +++ b/src/main/java/seedu/address/ui/NotificationWindow.java @@ -0,0 +1,79 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.util.Description; +import seedu.address.model.util.Quantity; + +/** + * Controller for notification page. + */ +public class NotificationWindow extends UiPart { + + public static final String NOTIFICATION_MESSAGE = "Product %s quantity low!\nRemaining balance: %s"; + + private static final Logger logger = LogsCenter.getLogger(NotificationWindow.class); + private static final String FXML = "NotificationWindow.fxml"; + + @FXML + private Label notificationMessage; + + /** + * Creates a new NotificationWindow. + * + * @param root Stage to use as root of the NotificationWindow. + */ + public NotificationWindow(Stage root) { + super(FXML, root); + } + + /** + * Creates a new NotificationWindow. + */ + public NotificationWindow() { + this(new Stage()); + } + + /** + * Shows the help window. + * @throws IllegalStateException + *
    + *
  • + * if this method is called on a thread other than the JavaFX Application Thread. + *
  • + *
  • + * if this method is called during animation or layout processing. + *
  • + *
  • + * if this method is called on the primary stage. + *
  • + *
  • + * if {@code dialogStage} is already showing. + *
  • + *
+ */ + public void show(Description description, Quantity quantity) { + logger.fine("Showing notification window."); + notificationMessage.setText(String.format(NOTIFICATION_MESSAGE, description, quantity)); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Returns true if the notification window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Focuses on the notification window. + */ + public void focus() { + getRoot().requestFocus(); + } +} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index 1328917096e..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,46 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/PlotWindow.java b/src/main/java/seedu/address/ui/PlotWindow.java new file mode 100644 index 00000000000..09ff384a1c1 --- /dev/null +++ b/src/main/java/seedu/address/ui/PlotWindow.java @@ -0,0 +1,80 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.chart.BarChart; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Label; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; + +/** + * Controller of a plot page. + */ +public class PlotWindow extends UiPart { + private static final Logger logger = LogsCenter.getLogger(PlotWindow.class); + private static final String FXML = "PlotWindow.fxml"; + + @FXML + private BarChart barChart; + + @FXML + private Label title; + + /** + * Creates a new PlotWindow. + * + * @param root Stage to use as the root of the HelpWindow. + */ + public PlotWindow(Stage root) { + super(FXML, root); + } + + /** + * Creates a new PlotWindow. + */ + public PlotWindow() { + this(new Stage()); + } + + /** + * Shows the plot window. + * @throws IllegalStateException + *
    + *
  • + * if this method is called on a thread other than the JavaFX Application Thread. + *
  • + *
  • + * if this method is called during animation or layout processing. + *
  • + *
  • + * if this method is called on the primary stage. + *
  • + *
  • + * if {@code dialogStage} is already showing. + *
  • + *
+ */ + public void show(XYChart.Series dataSeries, String titleText) { + logger.fine("Showing plot window."); + barChart.getData().setAll(dataSeries); + title.setText(titleText); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Returns true if the plot window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Focuses on the plot window. + */ + public void focus() { + getRoot().requestFocus(); + } +} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 876621d79b9..96d810d77ba 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -20,7 +20,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/inventory_system_logo.png"; private Logic logic; private MainWindow mainWindow; diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/customer/CustomerCard.java similarity index 51% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/address/ui/customer/CustomerCard.java index 0684b088868..f48cc4b9721 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/customer/CustomerCard.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.address.ui.customer; import java.util.Comparator; @@ -7,27 +7,28 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.person.Person; +import seedu.address.model.customer.Customer; +import seedu.address.ui.UiPart; /** - * An UI component that displays information of a {@code Person}. + * An UI component that displays information of a {@code Customer}. */ -public class PersonCard extends UiPart { +public class CustomerCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final String FXML = "CustomerListCard.fxml"; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. * As a consequence, UI elements' variable names cannot be set to such keywords * or an exception will be thrown by JavaFX during runtime. * - * @see The issue on AddressBook level 4 + * @see The issue on InventorySystem level 4 */ - public final Person person; + public final Customer customer; @FXML - private HBox cardPane; + private HBox cardPaneCustomer; @FXML private Label name; @FXML @@ -41,17 +42,21 @@ public class PersonCard extends UiPart { @FXML private FlowPane tags; - public PersonCard(Person person, int displayedIndex) { + public CustomerCard(Customer customer, int displayedIndex) { super(FXML); - this.person = person; + this.customer = customer; id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() + name.setText(customer.getName().fullName); + phone.setText(customer.getPhone().value); + address.setText(customer.getAddress().value); + email.setText(customer.getEmail().value); + customer.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + .forEach(tag -> { + Label newLabel = new Label(tag.tagName); + newLabel.setStyle("-fx-background-color:#242424"); + tags.getChildren().add(newLabel); + }); } @Override @@ -62,13 +67,13 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof PersonCard)) { + if (!(other instanceof CustomerCard)) { return false; } // state check - PersonCard card = (PersonCard) other; + CustomerCard card = (CustomerCard) other; return id.getText().equals(card.id.getText()) - && person.equals(card.person); + && customer.equals(card.customer); } } diff --git a/src/main/java/seedu/address/ui/customer/CustomerListPanel.java b/src/main/java/seedu/address/ui/customer/CustomerListPanel.java new file mode 100644 index 00000000000..b67ab191ab1 --- /dev/null +++ b/src/main/java/seedu/address/ui/customer/CustomerListPanel.java @@ -0,0 +1,48 @@ +package seedu.address.ui.customer; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.customer.Customer; +import seedu.address.ui.UiPart; + +/** + * Panel containing the list of customers. + */ +public class CustomerListPanel extends UiPart { + private static final String FXML = "CustomerListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(CustomerListPanel.class); + + @FXML + private ListView customerListView; + + public CustomerListPanel(ObservableList customerList) { + super(FXML); + customerListView.setItems(customerList); + logger.fine("Linked customer list to panel."); + customerListView.setCellFactory(listView -> new CustomerListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Customer} using a {@code CustomerCard}. + */ + class CustomerListViewCell extends ListCell { + @Override + protected void updateItem(Customer customer, boolean empty) { + super.updateItem(customer, empty); + + if (empty || customer == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new CustomerCard(customer, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/product/ProductCard.java b/src/main/java/seedu/address/ui/product/ProductCard.java new file mode 100644 index 00000000000..952e9f4493f --- /dev/null +++ b/src/main/java/seedu/address/ui/product/ProductCard.java @@ -0,0 +1,109 @@ +package seedu.address.ui.product; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.product.Product; +import seedu.address.ui.UiPart; + +/** + * An UI component that displays information of a {@code Product}. + */ +public class ProductCard extends UiPart { + + private static final String FXML = "ProductListCard.fxml"; + + private static final String RED_BAR = "red-bar"; + private static final String ORANGE_BAR = "orange-bar"; + private static final String YELLOW_BAR = "yellow-bar"; + private static final String GREEN_BAR = "green-bar"; + private static final String DISPLAY_CURRENCY = "$"; + private static final String[] barColorStyleClasses = { RED_BAR, ORANGE_BAR, YELLOW_BAR, GREEN_BAR }; + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on InventorySystem level 4 + */ + + public final Product product; + + @FXML + private HBox cardPaneProduct; + @FXML + private Label id; + @FXML + private Label description; + @FXML + private Label costPrice; + @FXML + private Label price; + @FXML + private Label quantity; + @FXML + private Label sales; + @FXML + private Label threshold; + @FXML + private ProgressBar progressBar; + + private double progress; + + public ProductCard(Product product, int displayedIndex) { + super(FXML); + this.product = product; + id.setText(displayedIndex + ". "); + description.setText(product.getDescription().value); + costPrice.setText(DISPLAY_CURRENCY + product.getCostPrice().value); + price.setText(DISPLAY_CURRENCY + product.getPrice().value); + quantity.setText(String.valueOf(product.getQuantity().getValue())); + sales.setText(DISPLAY_CURRENCY + product.getMoney().value); + threshold.setText(product.getThreshold().toString()); + updateProgressBar(); + } + + /** + * Updates the progress bar to visualise remaining stock quantity. + */ + private void updateProgressBar() { + progress = product.getQuantity().getValue() / (product.getThreshold().getDouble() * 5); + product.setProgress(progress); + progressBar.setProgress(progress); + if (progress <= 0.2) { + setBarStyleClass(progressBar, RED_BAR); + } else if (progress <= 0.4) { + setBarStyleClass(progressBar, ORANGE_BAR); + } else if (progress <= 0.6) { + setBarStyleClass(progressBar, YELLOW_BAR); + } else { + setBarStyleClass(progressBar, GREEN_BAR); + } + } + + private void setBarStyleClass(ProgressBar bar, String barStyleClass) { + bar.getStyleClass().removeAll(barColorStyleClasses); + bar.getStyleClass().add(barStyleClass); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ProductCard)) { + return false; + } + + // state check + ProductCard card = (ProductCard) other; + return id.getText().equals(card.id.getText()) + && product.equals(card.product); + } +} + diff --git a/src/main/java/seedu/address/ui/product/ProductListPanel.java b/src/main/java/seedu/address/ui/product/ProductListPanel.java new file mode 100644 index 00000000000..5b806e26663 --- /dev/null +++ b/src/main/java/seedu/address/ui/product/ProductListPanel.java @@ -0,0 +1,48 @@ +package seedu.address.ui.product; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.product.Product; +import seedu.address.ui.UiPart; + +/** + * Panel containing the list of persons. + */ +public class ProductListPanel extends UiPart { + private static final String FXML = "ProductListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ProductListPanel.class); + + @FXML + private ListView productListView; + + public ProductListPanel(ObservableList productList) { + super(FXML); + productListView.setItems(productList); + logger.fine("Linked product list to panel."); + productListView.setCellFactory(listView -> new ProductListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Customer} using a {@code CustomerCard}. + */ + class ProductListViewCell extends ListCell { + @Override + protected void updateItem(Product product, boolean empty) { + super.updateItem(product, empty); + + if (empty || product == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ProductCard(product, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/statistics/StatisticsCard.java b/src/main/java/seedu/address/ui/statistics/StatisticsCard.java new file mode 100644 index 00000000000..44de54a32a4 --- /dev/null +++ b/src/main/java/seedu/address/ui/statistics/StatisticsCard.java @@ -0,0 +1,77 @@ +package seedu.address.ui.statistics; + +import java.util.List; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; + +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; +import seedu.address.ui.UiPart; + +/** + * An UI component that displays information of a {@code Product}. + */ +public class StatisticsCard extends UiPart { + + private static final String FXML = "StatisticsListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on InventorySystem level 4 + */ + + public final Product product; + + @FXML + private HBox cardPane; + @FXML + private Label id; + @FXML + private Label description; + @FXML + private Label price; + @FXML + private Label quantity; // quantity of product since creation + @FXML + private Label sales; + @FXML + private Label costPrice; + @FXML + private Label profit; + + public StatisticsCard(Product product, int displayedIndex, List transactions) { + super(FXML); + this.product = product; + id.setText(displayedIndex + ". "); + description.setText(product.getDescription().value); + costPrice.setText("$" + product.getCostPrice().value); + price.setText("$" + product.getPrice().value); + quantity.setText(String.valueOf(product.getQuantitySold(transactions))); + sales.setText("$" + product.getMoney().value); + profit.setText("$" + product.getProfit(transactions)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof StatisticsCard)) { + return false; + } + + // state check + StatisticsCard card = (StatisticsCard) other; + return id.getText().equals(card.id.getText()) + && product.equals(card.product); + } +} diff --git a/src/main/java/seedu/address/ui/statistics/StatisticsListPanel.java b/src/main/java/seedu/address/ui/statistics/StatisticsListPanel.java new file mode 100644 index 00000000000..7bff97729bf --- /dev/null +++ b/src/main/java/seedu/address/ui/statistics/StatisticsListPanel.java @@ -0,0 +1,144 @@ +package seedu.address.ui.statistics; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.XYChart; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; +import seedu.address.ui.UiPart; + +/** + * Panel containing the list of persons. + */ +public class StatisticsListPanel extends UiPart { + private static final String FXML = "StatisticsListPanel.fxml"; + private static List transactions = new ArrayList<>(); + private static List products = new ArrayList<>(); + private final Logger logger = LogsCenter.getLogger(StatisticsListPanel.class); + + @FXML + private ListView statisticsListView; + + @FXML + private LineChart quantityLineChart; + + @FXML + private LineChart salesLineChart; + + + public StatisticsListPanel(Logic logic) { + super(FXML); + transactions = logic.getInventorySystem().getTransactionList(); + products = logic.getInventorySystem().getProductList(); + statisticsListView.setItems(getSortedProductList(logic)); + logger.fine("Linked top selling product list to panel."); + statisticsListView.setCellFactory(listView -> new StatisticsListViewCell()); + + quantityLineChart.getData().setAll(fetchProductQuantity()); + salesLineChart.getData().setAll(fetchProductSales()); + logger.fine("Fetched product quantity and sales."); + } + + /** + * Sorts the product list according to product revenue. + * @param logic + * @return sorted list + */ + private ObservableList getSortedProductList(Logic logic) { + List modifiableProducts = new ArrayList<>(logic.getInventorySystem().getProductList()); + + Collections.sort(modifiableProducts, new Comparator() { + @Override + public int compare(Product o1, Product o2) { + int o1Sales = o1.getProfit(transactions); + int o2Sales = o2.getProfit(transactions); + return o2Sales - o1Sales; + } + }); + + return FXCollections.observableArrayList(modifiableProducts); + } + + /** + * Sorts the product list according to product revenue. + * @return sorted list + */ + private XYChart.Series fetchProductQuantity() { + XYChart.Series dataSeries = new XYChart.Series(); + HashMap allProductQuantities = new HashMap<>(); + + products.forEach(p -> { + if (allProductQuantities.containsKey(p.getQuantity().getValue())) { + int oldValue = allProductQuantities.get(p.getQuantity().getValue()); + allProductQuantities.put(p.getQuantity().getValue(), oldValue + 1); + } else { + allProductQuantities.put(p.getQuantity().getValue(), 1); + } + }); + + for (Map.Entry entry: allProductQuantities.entrySet()) { + XYChart.Data nextData = new XYChart.Data(entry.getKey().intValue(), entry.getValue().intValue()); + dataSeries.getData().add(nextData); + } + + return dataSeries; + } + + /** + * Sorts the product list according to product revenue. + * @return sorted list + */ + private XYChart.Series fetchProductSales() { + XYChart.Series dataSeries = new XYChart.Series(); + HashMap allProductSales = new HashMap<>(); + + products.forEach(p -> { + if (allProductSales.containsKey(p.getMoney().value)) { + int oldValue = allProductSales.get(p.getMoney().value); + allProductSales.put(p.getMoney().value, oldValue + 1); + } else { + allProductSales.put(p.getMoney().value, 1); + } + }); + + for (Map.Entry entry: allProductSales.entrySet()) { + XYChart.Data nextData = new XYChart.Data(entry.getKey().intValue(), entry.getValue().intValue()); + dataSeries.getData().add(nextData); + } + + return dataSeries; + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Customer} using a {@code CustomerCard}. + */ + class StatisticsListViewCell extends ListCell { + @Override + protected void updateItem(Product product, boolean empty) { + super.updateItem(product, empty); + + if (empty || product == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new StatisticsCard(product, getIndex() + 1, transactions).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/transaction/TransactionListPanel.java b/src/main/java/seedu/address/ui/transaction/TransactionListPanel.java new file mode 100644 index 00000000000..ff1fe3d5e1c --- /dev/null +++ b/src/main/java/seedu/address/ui/transaction/TransactionListPanel.java @@ -0,0 +1,90 @@ +package seedu.address.ui.transaction; + +import java.util.logging.Logger; + +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.transaction.Transaction; +import seedu.address.ui.UiPart; + +/** + * Panel containing the list of transactions. + */ +public class TransactionListPanel extends UiPart { + private static final String FXML = "TransactionListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TransactionListPanel.class); + + @FXML + private TableView transactionTableView; + + @FXML + private TableColumn customerCol; + + @FXML + private TableColumn productCol; + + @FXML + private TableColumn dateTimeCol; + + @FXML + private TableColumn quantityCol; + + @FXML + private TableColumn amountCol; + + @FXML + private TableColumn descriptionCol; + + public TransactionListPanel(ObservableList transactionList) { + super(FXML); + transactionTableView.setItems(transactionList); + customerCol.setCellValueFactory(t -> { + SimpleStringProperty property = new SimpleStringProperty(); + property.setValue(t.getValue().getCustomer().getName().toString()); + return property; + }); + customerCol.setSortable(false); + + productCol.setCellValueFactory(t -> { + SimpleStringProperty property = new SimpleStringProperty(); + property.setValue(t.getValue().getProduct().getDescription().toString()); + return property; + }); + productCol.setSortable(false); + + dateTimeCol.setCellValueFactory(t -> { + SimpleStringProperty property = new SimpleStringProperty(); + property.setValue(t.getValue().getDateTime().toString()); + return property; + }); + dateTimeCol.setSortable(false); + + quantityCol.setCellValueFactory(t -> { + SimpleStringProperty property = new SimpleStringProperty(); + property.setValue(t.getValue().getQuantity().toString()); + return property; + }); + quantityCol.setSortable(false); + + amountCol.setCellValueFactory(t -> { + SimpleStringProperty property = new SimpleStringProperty(); + property.setValue(t.getValue().getMoney().toString()); + return property; + }); + amountCol.setSortable(false); + + descriptionCol.setCellValueFactory(t -> { + SimpleStringProperty property = new SimpleStringProperty(); + property.setValue(t.getValue().getDescription().toString()); + return property; + }); + descriptionCol.setSortable(false); + logger.fine("Linked transaction list to panel."); + } +} + diff --git a/src/main/resources/html/userGuide.html b/src/main/resources/html/userGuide.html new file mode 100644 index 00000000000..af0a578bf89 --- /dev/null +++ b/src/main/resources/html/userGuide.html @@ -0,0 +1,1918 @@ + + + + + + + +iTrack Pro - User Guide + + + + + +
+
+
+
+

By: AY1920S2-CS2103-T09-2 Since: Feb 2020 Licence: MIT

+
+
+ +
+
+

1. Introduction

+
+
+

iTrack Pro is for the grocery shop owners to keep track of the products, customers, and transactions of the shop and have a better understanding of the business by viewing the performance of products and behaviors of customers. It is also able to provide an analysis of the entire business to help the owner manage the shop.

+
+
+
+
+

2. Quick Start

+
+
+
    +
  1. +

    Ensure you have Java 11 or above installed in your Computer.

    +
  2. +
  3. +

    Download the latest iTrackPro.jar here.

    +
  4. +
  5. +

    Copy the file to the folder you want to use as the home folder for your Inventory System.

    +
  6. +
  7. +

    Double-click the file to start the app. The GUI should appear in a few seconds.

    +
    +
    +Ui +
    +
    +
  8. +
  9. +

    Type the command in the command box and press Enter to execute it.
    +e.g. typing help and pressing Enter will open the help window.

    +
  10. +
  11. +

    Some example commands you can try:

    +
    +
      +
    • +

      listc : lists all customers

      +
    • +
    • +

      addc n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 : adds a customer to the application

      +
    • +
    • +

      deletec3 : deletes the 3rd customer shown in the current list

      +
    • +
    • +

      exit : exits the app

      +
    • +
    +
    +
  12. +
  13. +

    Refer to Section 4, “Features” for details of each command.

    +
  14. +
+
+
+
+
+

3. Legend

+
+
+

Please take note of the three different types of notification that are used in the features section:

+
+
+ + + + + +
+
Note
+
+This tells you some things to take note of. It also helps to explain why we did what we did. +
+
+
+ + + + + +
+
Tip
+
+This provides tips on using the command the best way, and alternatives that can be used. +
+
+
+ + + + + +
+
Warning
+
+Pay attention to the warnings before you execute the command, or else things may not go according to what you expect. +
+
+
+
+
+

4. Features

+
+
+
+
+

Command Format

+
+
+
    +
  • +

    Words in UPPER_CASE are the parameters to be supplied by the user e.g. in add n/NAME, NAME is a parameter which can be used as add n/John Doe.

    +
  • +
  • +

    Items in square brackets are optional e.g n/NAME [t/TAG] can be used as n/John Doe t/friend or as n/John Doe.

    +
  • +
  • +

    Items with ​ after them can be used multiple times including zero times e.g. [t/TAG]…​ can be used as   (i.e. 0 times), t/friend, t/friend t/family etc.

    +
  • +
  • +

    Parameters can be in any order e.g. if the command specifies n/NAME p/PHONE_NUMBER, p/PHONE_NUMBER n/NAME is also acceptable.

    +
  • +
+
+
+
+
+

4.1. General

+
+

4.1.1. Viewing help : help

+
+

Format: help

+
+
+
+

4.1.2. Receiving notification while a certain product is running low.

+
+

Pops up notification if quantity of product is below threshold.
+Displays the product name and remaining product quantity.

+
+
+
+PopupNotification +
+
+
+ + + + + +
+
Note
+
+This is a passive feature. +
+
+
+
+

4.1.3. Exiting from the program : exit

+
+

Exits from the program, closes all opened windows.
+Format: exit

+
+
+
+

4.1.4. Saving the data

+
+

The application data is saved in the hard disk automatically after any command that changes the data.

+
+
+ + + + + +
+
Note
+
+There is no need to save manually. +
+
+
+
+

4.1.5. Reusing previous inputs

+
+

The application keeps the history of previous inputs that was keyed in the command line.

+
+
+
+
+
    +
  • +

    Can keep history of up to 100 inputs.

    +
  • +
  • +

    Up arrow key in command line to navigate up the history of inputs.

    +
  • +
  • +

    Down arrow key in command line to navigate down the history of inputs.

    +
  • +
  • +

    Commands are saved into the history automatically.

    +
  • +
+
+
+
+
+ + + + + +
+
Warning
+
+The history is deleted after application closes. +
+
+
+
+

4.1.6. Keeping track of products that are running low on stock

+
+

The product list updates and sorts by the progress bar indicator when user uses the listp command.
+Products are ordered by the level of progress bar indicator. (E.g. the lower the bar, the higher it is in the list). +This is to help the user easier to know which products are running low on stock.

+
+
+
+ProductCard +
+
+
+
+
+
    +
  • +

    The progress bar indicator beside the products' name visualises the remaining balance.

    +
  • +
  • +

    The bar color depends on quantity / threshold * 5, and changes as the level decreases, from green → yellow → orange → red.

    +
  • +
+
+
+
+
+ + + + + +
+
Note
+
+This is a passive feature.
+Green: >0.6
+Yellow: 0.6
+Orange: 0.4
+Red: 0.2 +
+
+
+
+
+

4.2. Customer

+
+

4.2.1. Adding a customer: addc

+
+

Adds a customer to the customer list
+Format: addc n/NAME p/PHONE_NUMBER [e/EMAIL] [a/ADDRESS] [t/TAG]…

+
+
+
+
+
    +
  • +

    A customer can have up to 5 tags (including 0).

    +
  • +
  • +

    Duplicate customers (with the same name, phone, email, address) are not allowed.

    +
  • +
  • +

    The address field can take up to 45 characters.

    +
  • +
  • +

    The name field can take up to 30 characters.

    +
  • +
  • +

    The phone field can be between 3 to 15 characters long and contain only integers.

    +
  • +
  • +

    The email field can take up to 40 characters and should be a valid email format.

    +
  • +
+
+
+
+
+ + + + + +
+
Tip
+
+The email field [e/] is optional, and will be recorded as N/A if left empty.
+The address field [a/] is optional, and will be recorded as N/A if left empty. +
+
+
+

Examples:

+
+
+
    +
  • +

    addc n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01

    +
  • +
  • +

    addc n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal

    +
  • +
+
+
+
+

4.2.2. Listing all customers: listc

+
+

Shows a list of all customers in the customer list.
+Format: listc

+
+
+
+

4.2.3. Editing a customer: editc

+
+

Edits an existing customer in the customer list.
+Format: editc INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​

+
+
+
+
+
    +
  • +

    Edits the customer at the specified INDEX. The index refers to the index number shown in the displayed customer list. The index must be a positive integer 1, 2, 3, …​

    +
  • +
  • +

    At least one of the optional fields must be provided.

    +
  • +
  • +

    Existing values will be updated to the input values.

    +
  • +
  • +

    When editing tags, the existing tags of the customer will be removed i.e adding of tags is not cumulative.

    +
  • +
  • +

    You can remove all the customer’s tags by typing t/ without specifying any tags after it.

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    editc 1 p/91234567 e/johndoe@example.com
    +Edits the phone number and email address of the 1st customer to be 91234567 and johndoe@example.com respectively.

    +
  • +
  • +

    editc 2 n/Betsy Crower t/
    +Edits the name of the 2nd customer to be Betsy Crower and clears all existing tags.

    +
  • +
+
+
+
+

4.2.4. Finding customers: findc

+
+

Finds customers whose attributes match the given attributes.
+Format: findc [n/NAME [NAME]…​] [p/PHONE] [e/EMAIL] [a/ADDRESS [ADDRESS]…​]

+
+
+
+
+
    +
  • +

    The search is case insensitive. e.g hans will match Hans

    +
  • +
  • +

    At least one of the optional fields must be provided.

    +
  • +
  • +

    Only full words will be matched for keywords e.g. Han will not match Hans

    +
  • +
  • +

    The order of the keywords and attributes does not matter. e.g. Hans Bo will match Bo Hans, n/Bob a/31 will match a/31 n/Bob

    +
  • +
  • +

    Only customers matching all attributes will be returned (i.e. AND search) e.g. findc n/Jane a/31 will return customers with 'jane' in their names and '31' in their addresses

    +
  • +
  • +

    Customers matching any keywords in an attribute (for name and address only) will be returned (i.e. OR search) e.g. findc a/clementi ave will return customers with 'clementi' or 'ave' in their addresses

    +
  • +
+
+
+
+
+
+FindCustomerByAddress +
+
+
+
+FindCustomerByAddress2 +
+
+
+ + + + + +
+
Note
+
+Only name and address support multiple keywords +
+
+
+

Examples:

+
+
+
    +
  • +

    findc n/John
    +Returns all customers with names John from the customer list.

    +
  • +
  • +

    findc n/Betsy Tim John
    +Returns any customer having names Betsy, Tim, OR John in the customer list.

    +
  • +
  • +

    findc a/serangoon n/Bob
    +Returns all customers with addresses in Serangoon AND Bob in their names.

    +
  • +
+
+
+
+

4.2.5. Deleting a customer: deletec

+
+

Deletes the specified customer from the customer list that is currently being displayed.
+Format: deletec INDEX

+
+
+
+
+
    +
  • +

    Deletes the customer at the specified INDEX.

    +
  • +
  • +

    The index refers to the index number shown in the displayed customer list.

    +
  • +
  • +

    The index must be a positive integer 1, 2, 3, …​

    +
  • +
+
+
+
+
+ + + + + +
+
Warning
+
+Deletes transactions that are associated with this particular customer as well. +
+
+
+

Examples:

+
+
+
    +
  • +

    listc
    +deletec 2
    +Deletes the 2nd customer in the customer list.

    +
  • +
  • +

    findc n/Betsy
    +deletec 1
    +Deletes the 1st customer in the results of the find command.

    +
  • +
+
+
+
+

4.2.6. Clearing all customers: clearc

+
+

Clears all entries from the customer list.
+Format: clearc

+
+
+ + + + + +
+
Warning
+
+Permanently deletes all the stored customer data in the application.
+Deletes all transactions as well. +
+
+
+
+
+

4.3. Product

+
+

4.3.1. Adding a product: addp

+
+

Adds a product to the product list.
+Format: addp d/DESCRIPTION pr/PRICE q/QUANTITY cp/COSTPRICE [s/SALES]

+
+
+
+
+
    +
  • +

    Duplicate products (with the same description, cost price, price) are not allowed.

    +
  • +
  • +

    The price, cost price, quantity and sales can take integers up to 1000000.

    +
  • +
  • +

    The price and cost price must be at least $1.

    +
  • +
+
+
+
+
+ + + + + +
+
Note
+
+The default threshold value is 20% of quantity of product. +
+
+
+ + + + + +
+
Tip
+
+A product created without providing values for sales (in SGD) will be created with 0 sales. +
+
+
+
+AddProduct +
+
+
+

Examples:

+
+
+
    +
  • +

    addp d/iphone x pr/1000 q/10 cp/300

    +
  • +
  • +

    addp d/camera pr/2000 q/90 s/100 cp/1000

    +
  • +
+
+
+
+

4.3.2. Listing all products : listp

+
+

Show all products in the product list.
+Format: listp

+
+
+ + + + + +
+
Note
+
+Sorts all of the product by the product quantity, represented by the bar indicator beside the product name. +
+
+
+
+

4.3.3. Editing a product : editp

+
+

Edits an existing product in the displayed product list.
+Format: editp INDEX [d/DESCRIPTION] [pr/PRICE] [q/QUANTITY] [cp/COSTPRICE] [s/SALES]

+
+
+
+
+
    +
  • +

    Edits the product at the specified INDEX. The index refers to the index number shown in the displayed product list. The index must be a positive integer 1, 2, 3, …​

    +
  • +
  • +

    At least one of the optional fields must be provided.

    +
  • +
  • +

    Existing values will be updated to the input values.

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    editp 1 pr/1150 q/80
    +Edits the price and quantity of the 1st product in the list to be $1150 and 80 respectively.

    +
  • +
  • +

    editp 2 s/1000
    +Edits the sales of the 2nd product in the list to be $1000.

    +
  • +
+
+
+
+

4.3.4. Finding products : findp

+
+

Finds products whose description contains a certain keyword
+Format: findp KEYWORD [KEYWORD]…​

+
+
+
+
+
    +
  • +

    The search is case insensitive. e.g blue will match Blue

    +
  • +
  • +

    At least one of the option fields must be provided.

    +
  • +
  • +

    Only full words will be matched for keywords e.g. blu will not match blue

    +
  • +
  • +

    The keyword will be searched only in the product’s description.

    +
  • +
  • +

    The order of the keywords does not matter. e.g. blue shoes will match shoes blue

    +
  • +
  • +

    Products matching at least one keyword will be returned (i.e. OR search). e.g. blue shoes will return blue slippers, red shoes

    +
  • +
+
+
+
+
+
+FindProduct +
+
+
+

Examples:

+
+
+
    +
  • +

    findp camera
    +Returns all product with description camera in it.

    +
  • +
  • +

    findp iphone
    +Returns all product with description iPhone in it.

    +
  • +
+
+
+
+

4.3.5. Deleting a product : deletep

+
+

Deletes the specified product from the system.
+Format: deletep INDEX

+
+
+
+
+
    +
  • +

    Deletes the product at the specified INDEX.

    +
  • +
  • +

    The index refers to the index number shown in the displayed product list.

    +
  • +
  • +

    The index must be a positive integer 1, 2, 3, …​

    +
  • +
+
+
+
+
+ + + + + +
+
Warning
+
+Deletes transactions that are associated with this particular product as well. +
+
+
+

Examples:

+
+
+
    +
  • +

    listp
    +deletep 2
    +Deletes the 2nd product in the product list.

    +
  • +
  • +

    findp camera
    +deletep 1
    +Deletes the 1st product in the results of the find command.

    +
  • +
+
+
+
+

4.3.6. Clearing all products : clearp

+
+

Clears all entries from the product list.
+Format: clearp

+
+
+ + + + + +
+
Warning
+
+Permanently deletes all the stored product data in the application.
+Deletes all transactions as well. +
+
+
+
+
+

4.4. Transaction

+
+

User can navigate to the transaction panel by clicking on the transactions tab.

+
+
+
+TransactionTab +
+
+
+

4.4.1. Adding a transaction : addt

+
+

Adds a specified transaction to the system.
+Format: addt p/PRODUCT_ID c/CUSTOMER_ID q/QUANTITY [dt/DATETIME] [m/MONEY] [d/DESCRIPTION]

+
+
+
+
+
    +
  • +

    Duplicate transactions (with the same customer, product, datetime) are not allowed.

    +
  • +
  • +

    The quantity and money fields can take integers up to 1000000.

    +
  • +
  • +

    The quantity must be at least 1.

    +
  • +
+
+
+
+
+ + + + + +
+
Tip
+
+The date time field [dt/] is optional, and will be recorded as current local machine time if left empty.
+The money field [m/] is optional, and will be recorded as product price multiplied by quantity if left empty. +Only need to enter if necessary (i.e. discounts on products).
+The description field [d/] is optional, will be recorded as N/A if left empty. +Only need to enter if user wants to add notes to the transaction. +
+
+
+
+AddTransaction +
+
+
+

After adding transactions, the transaction list can be viewed under the transaction tab.

+
+
+
+AddTransaction2 +
+
+
+

Examples:

+
+
+
    +
  • +

    addt p/1 c/10 dt/2020-02-19 19:00 q/10 m/20
    +Adds a transaction, where the 10th customer bought 10 of the 1st product for $20 at 2020-02-19 19:00.

    +
  • +
  • +

    addt p/20 c/2 dt/2020-02-20 10:00 q/10 m/30 d/under discount
    +Adds a transaction, where the 2nd customer bought 10 of the 20th product for $30 at 2020-02-20 10:00 at an discount.

    +
  • +
+
+
+
+

4.4.2. Listing all transactions : listt

+
+

Lists all the transactions.
+Format: listt

+
+
+
+

4.4.3. Editing a transaction : editt

+
+

Edits a transaction in the system. It allows the user to edit wrong transction + with correct information.
+Format: editt INDEX [p/PRODUCT_ID] [c/CUSTOMER_ID] [dt/DATE_TIME] [q/QUANTITY] [m/MONEY] [d/DESCRIPTION]

+
+
+
+
+
    +
  • +

    Edits the transaction at the specified INDEX. The index refers to the index number shown in the displayed transaction list. The index must be a positive integer 1, 2, 3, …​

    +
  • +
  • +

    At least one of the optional fields must be provided.

    +
  • +
  • +

    Existing values will be updated to the input values.

    +
  • +
+
+
+
+
+

Examples:

+
+
+
    +
  • +

    editt 1 p/101 c/123
    +Edits the product id and customer id of the 1st transaction to be 101 and 123 respectively.

    +
  • +
+
+
+
+

4.4.4. Finding transactions : findt

+
+

Finds transactions whose attributes match the given attributes.
+Format: findt [p/PRODUCT_NAME [PRODUCT_NAME]…​] [c/CUSTOMER_NAME [CUSTOMER_NAME]…​] [dt/DATE_TIME] [m/MONEY]

+
+
+
+
+
    +
  • +

    The search is case insensitive. e.g blue will match Blue

    +
  • +
  • +

    At least one of the option fields must be provided.

    +
  • +
  • +

    The order of the keywords and attributes does not matter. e.g. Hans Bo will match Bo Hans, c/Bob m/31 will match m/31 c/Bob

    +
  • +
  • +

    Only transactions matching all attributes will be returned (i.e. AND search). e.g. findt c/Jane p/bag will return transactions with 'jane' in their customer names and 'bag' in their product names

    +
  • +
  • +

    Transactions matching any keywords in an attribute (for product or customer name only) will be returned (i.e. OR search) e.g. findt c/jane avery will return transactions with 'jane' or 'avery' in their customer names

    +
  • +
+
+
+
+
+
+FindTransactionByProduct +
+
+
+
+FindTransactionByCustomer +
+
+
+ + + + + +
+
Note
+
+Only product name and customer name support multiple keywords +
+
+
+

Examples:

+
+
+
    +
  • +

    findt p/iphone
    +Returns all transactions that involve the product iphone.

    +
  • +
  • +

    findt c/bob angie
    +Returns all transactions that involve a customer named Bob OR a customer named Angie.

    +
  • +
  • +

    findt dt/2020-02-07 16:00
    +Returns all transactions made on 7th February 2020 4pm.

    +
  • +
  • +

    findt m/100
    +Returns all transactions that have an amount of 100 dollars.

    +
  • +
  • +

    findt c/bob dt/2020-02-07 16:00
    +Returns all transactions that Bob made on the 7th February 2020 4pm.

    +
  • +
+
+
+
+

4.4.5. Undo a transaction : undot

+
+

Undo the specified transaction from the system. It allows the user to remove a transaction in case he/she keyed inaccurate +information.
+Format: undot INDEX

+
+
+
+
+
    +
  • +

    Undos the transaction at the specified INDEX.

    +
  • +
  • +

    The index refers to the index number shown in the displayed transaction list.

    +
  • +
  • +

    The index must be a positive integer 1, 2, 3, …​

    +
  • +
+
+
+
+
+ + + + + +
+
Note
+
+Why undot instead of deletet? Deletet implies that transaction is only deleted but undot is more fitting as the product details will be modified too. +
+
+
+ + + + + +
+
Warning
+
+Adds the quantity in the transaction back to the product and reduces the sales of the product by transaction amount. +
+
+
+

Examples:

+
+
+
    +
  • +

    listt
    +undot 2
    +Undo the 2nd transaction in the displayed list.

    +
  • +
  • +

    findt dt/2020-01-03 16:00
    +undot 1
    +Undo the 1st transaction in the results of the find command.

    +
  • +
+
+
+
+

4.4.6. Clearing all transactions : cleart

+
+

Clears all transactions from the list of transactions.
+Format: cleart

+
+
+ + + + + +
+
Warning
+
+Permanently deletes all the stored transaction data in the application. +
+
+
+
+
+

4.5. Statistics

+
+

User can navigate to the statistics panel by clicking on the statistics tab.

+
+
+
+StatisticsTab +
+
+
+

4.5.1. Viewing the top-selling and worst-selling products.

+
+

Displays and updates the top-selling and worst-selling products (sorted by profit) as transactions are made.

+
+
+ + + + + +
+
Note
+
+This is a passive feature. +
+
+
+
+TopSellingProducts +
+
+
+
+

4.5.2. Viewing the product sales and quantity histogram.

+
+

Displays and updates the histogram of product sales and quantity.
+Plots 2 graphs:
+1. Number of products against quantity of products
+2. Number of products against sales of products

+
+
+ + + + + +
+
Note
+
+This is a passive feature. +
+
+
+
+ViewInventory2 +
+
+
+
+

4.5.3. Getting the revenue made in a certain period : revenue

+
+

Gets the revenue made in a selected period. (start date to end date, both inclusive)
+Format: revenue [sd/START_DATE] [ed/END_DATE]

+
+
+
+
+
    +
  • +

    The start date and end date must follow a format of yyyy-mm-dd hh:mm

    +
  • +
  • +

    The start date must be before or equal to end date

    +
  • +
  • +

    At least one product must be present

    +
  • +
+
+
+
+
+

Example:

+
+
+
    +
  • +

    revenue sd/2020-01-01 10:00 ed/2020-12-31 10:01
    +Returns the revenue from Jan 1 2020 10am to Dec 31 2020 10:01am

    +
  • +
+
+
+
+

4.5.4. Getting the profit made in a certain period : profit

+
+

Gets the profit made in a selected period (start date to end date, both inclusive).
+Format: profit [sd/START_DATE] [ed/END_DATE]

+
+
+
+
+
    +
  • +

    The start date and end date must follow a format of yyyy-mm-dd hh:mm

    +
  • +
  • +

    The start date must be before or equal to end date

    +
  • +
  • +

    At least one product must be present

    +
  • +
+
+
+
+
+

Example:

+
+
+
    +
  • +

    profit sd/2020-01-01 10:00 ed/2020-12-31 10:01
    +Returns the profit from Jan 1 2020 10am to Dec 31 2020 10:01am

    +
  • +
+
+
+
+

4.5.5. Setting the low-inventory threshold : lowlimit

+
+

Sets the notification threshold for individual product and updates the bar indicator of the product.
+Format: lowlimit p/PRODUCT_ID t/THRESHOLD

+
+
+
+
+
    +
  • +

    THRESHOLD must be non-negative integers, i.e. 1, 2, 3, …​

    +
  • +
  • +

    PRODUCT_ID refers to the index number shown in the displayed products list.

    +
  • +
  • +

    The index must be a positive integer 1, 2, 3, …​

    +
  • +
+
+
+
+
+ + + + + +
+
Note
+
+The default threshold represents 20% of the desired quantity.
+The quantity threshold can take integers up to 1000000. +
+
+
+

Examples:

+
+
+
    +
  • +

    lowlimit p/1 t/20
    +Sets the low inventory threshold for the 1st product as 20.

    +
  • +
+
+
+
+

4.5.6. Predicting the sales for the next month : predict

+
+

Predicts sales for the next month based on sales in the previous three months
+Format: predict

+
+
+ + + + + +
+
Note
+
+The average of the profits made in the past three months is the predicted sales for next month. +
+
+
+
+

4.5.7. Plotting sales of a product: plotsales

+
+

Plots a graph with the sales of the selected product in a given time period.
+Format: plotsales PRODUCT_INDEX [sd/START_DATE] [ed/END_DATE]

+
+
+
+
+
    +
  • +

    The start date and end date must follow a format of yyyy-mm-dd hh:mm

    +
  • +
  • +

    The start date must be before or equal to end date

    +
  • +
  • +

    At least one product must be present

    +
  • +
+
+
+
+
+ + + + + +
+
Tip
+
+The start date and end date attributes are optional. If omitted, the system +will plot the last 7 days by default. +
+
+
+

Examples:

+
+
+
    +
  • +

    plotsales 1 sd/2020-02-20 10:00 ed/2020-02-28 10:01
    +Plots a graph with the sales of the selected product between 20th Feb 10am and 28th Feb 10:01am in 2020.

    +
  • +
  • +

    plotsales 1
    +Plots a graph with the sales of the selected product in the past week.

    +
  • +
+
+
+
+SalesPlot +
+
+
+
+
+
+
+

5. FAQ

+
+
+

Q: How to delete a product?
+A: First, display a list of product, e.g. listp. Then type deletep INDEX where the index refers to the index displayed in the list. Refer to Section 4.3.5, “Deleting a product : deletep.

+
+
+

Q: How do I transfer my data to another Computer?
+A: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Inventory System folder.

+
+
+
+
+

6. Command Summary

+
+
+
    +
  • +

    Adding customer : addc n/NAME p/PHONE_NUMBER [e/EMAIL] [a/ADDRESS] [t/TAG]…
    +e.g. addc n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01

    +
  • +
  • +

    Listing all customers : listc

    +
  • +
  • +

    Editing customer information : editc INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​
    +e.g. editc 1 p/91234567 e/johndoe@example.com

    +
  • +
  • +

    Finding customers : findc [n/NAME [NAME]…​] [p/PHONE] [e/EMAIL] [a/ADDRESS [ADDRESS]…​]
    +e.g. findc n/John

    +
  • +
  • +

    Deleting a customer : deletec INDEX
    +e.g. findc n/Betsy
    +deletec 1

    +
  • +
  • +

    Clearing all customers : clearc

    +
  • +
  • +

    Adding a product : addp d/DESCRIPTION pr/PRICE q/QUANTITY cp/COSTPRICE [s/SALES]
    +e.g. addp d/iphone x pr/1000 cp/800 q/10

    +
  • +
  • +

    Listing all products : listp

    +
  • +
  • +

    Editing a product : editp INDEX [d/DESCRIPTION] [pr/PRICE] [cp/COSTPRICE] [q/QUANTITY] [s/SALES]
    +e.g. editp 1 pr/1150 q/80

    +
  • +
  • +

    Finding products : findp KEYWORD [KEYWORD]…​
    +e.g. findp black

    +
  • +
  • +

    Deleting a product : deletep INDEX
    +e.g. listp
    +deletep 2

    +
  • +
  • +

    Clearing all products : clearp

    +
  • +
  • +

    Adding a transaction : addt p/PRODUCT_ID c/CUSTOMER_ID dt/DATE_TIME m/MONEY q/QUANTITY [d/DESCRIPTION]
    +e.g. addt p/20 c/2 dt/2020-02-20 10:00 m/30 q/10 d/under discount

    +
  • +
  • +

    Listing all transactions : listt

    +
  • +
  • +

    Editing a transaction : editt INDEX [p/PRODUCT_ID] [c/CUSTOMER_ID] [dt/DATE_TIME] [q/QUANTITY] [m/MONEY] [d/DESCRIPTION]
    +e.g. editt 1 p/101 c/123

    +
  • +
  • +

    Finding transactions : findt [p/PRODUCT_NAME [PRODUCT_NAME]…​] [c/CUSTOMER_NAME [CUSTOMER_NAME]…​] [dt/DATE_TIME] [m/MONEY]
    +e.g. findt c/bob dt/2020-02-07 10:00

    +
  • +
  • +

    Undo a transaction : undot INDEX
    +e.g. findt dt/2020-01-03 10:00
    +undot 1

    +
  • +
  • +

    Clearing all transactions : cleart

    +
  • +
  • +

    Get the revenue made in a certain period : revenue sd/START_DATE ed/END_DATE
    +e.g. revenue sd/2020-01-01 10:00 ed/2020-12-12 10:01

    +
  • +
  • +

    Get the profit made in a certain period : profit sd/START_DATE ed/END_DATE
    +e.g. profit sd/2020-01-01 10:00 ed/2020-12-12 10:01

    +
  • +
  • +

    Setting the low-inventory threshold : lowlimit p/PRODUCT_ID t/THRESHOLD
    +e.g. lowlimit p/1 t/20

    +
  • +
  • +

    Predicting the sales for the next month : predict

    +
  • +
  • +

    Plotting sales : plotsales PRODUCT_INDEX [sd/START_DATE] [ed/END_DATE]
    +e.g. plotsales 1 sd/2020-03-20 10:00 ed/2020-03-30 10:00

    +
  • +
  • +

    Exiting from the program : exit

    +
  • +
  • +

    Get help : help

    +
  • +
+
+
+
+
+ + + diff --git a/src/main/resources/images/AddProduct.png b/src/main/resources/images/AddProduct.png new file mode 100644 index 00000000000..1403a79bfd5 Binary files /dev/null and b/src/main/resources/images/AddProduct.png differ diff --git a/src/main/resources/images/AddTransaction.png b/src/main/resources/images/AddTransaction.png new file mode 100644 index 00000000000..fd1c8e2906d Binary files /dev/null and b/src/main/resources/images/AddTransaction.png differ diff --git a/src/main/resources/images/AddTransaction2.png b/src/main/resources/images/AddTransaction2.png new file mode 100644 index 00000000000..8255fc4d4f3 Binary files /dev/null and b/src/main/resources/images/AddTransaction2.png differ diff --git a/src/main/resources/images/FindCustomerByAddress.png b/src/main/resources/images/FindCustomerByAddress.png new file mode 100644 index 00000000000..5bf888fa9f1 Binary files /dev/null and b/src/main/resources/images/FindCustomerByAddress.png differ diff --git a/src/main/resources/images/FindCustomerByAddress2.png b/src/main/resources/images/FindCustomerByAddress2.png new file mode 100644 index 00000000000..1d71c62c5ad Binary files /dev/null and b/src/main/resources/images/FindCustomerByAddress2.png differ diff --git a/src/main/resources/images/FindProduct.png b/src/main/resources/images/FindProduct.png new file mode 100644 index 00000000000..61eaf9f754e Binary files /dev/null and b/src/main/resources/images/FindProduct.png differ diff --git a/src/main/resources/images/FindTransactionByCustomer.png b/src/main/resources/images/FindTransactionByCustomer.png new file mode 100644 index 00000000000..35c2fef2cd3 Binary files /dev/null and b/src/main/resources/images/FindTransactionByCustomer.png differ diff --git a/src/main/resources/images/FindTransactionByProduct.png b/src/main/resources/images/FindTransactionByProduct.png new file mode 100644 index 00000000000..e013e9cf00e Binary files /dev/null and b/src/main/resources/images/FindTransactionByProduct.png differ diff --git a/src/main/resources/images/PopupNotification.png b/src/main/resources/images/PopupNotification.png new file mode 100644 index 00000000000..5638659645d Binary files /dev/null and b/src/main/resources/images/PopupNotification.png differ diff --git a/src/main/resources/images/ProductCard.png b/src/main/resources/images/ProductCard.png new file mode 100644 index 00000000000..a7f07e23170 Binary files /dev/null and b/src/main/resources/images/ProductCard.png differ diff --git a/src/main/resources/images/SalesPlot.png b/src/main/resources/images/SalesPlot.png new file mode 100644 index 00000000000..ee0edf3ac2f Binary files /dev/null and b/src/main/resources/images/SalesPlot.png differ diff --git a/src/main/resources/images/StatisticsTab.png b/src/main/resources/images/StatisticsTab.png new file mode 100644 index 00000000000..a7cc58be1ab Binary files /dev/null and b/src/main/resources/images/StatisticsTab.png differ diff --git a/src/main/resources/images/TopSellingProducts.png b/src/main/resources/images/TopSellingProducts.png new file mode 100644 index 00000000000..1007d7655e5 Binary files /dev/null and b/src/main/resources/images/TopSellingProducts.png differ diff --git a/src/main/resources/images/TransactionTab.png b/src/main/resources/images/TransactionTab.png new file mode 100644 index 00000000000..4f6b7924b20 Binary files /dev/null and b/src/main/resources/images/TransactionTab.png differ diff --git a/src/main/resources/images/Ui.png b/src/main/resources/images/Ui.png new file mode 100644 index 00000000000..9010b10dc15 Binary files /dev/null and b/src/main/resources/images/Ui.png differ diff --git a/src/main/resources/images/ViewInventory2.png b/src/main/resources/images/ViewInventory2.png new file mode 100644 index 00000000000..647e08d04f4 Binary files /dev/null and b/src/main/resources/images/ViewInventory2.png differ diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png deleted file mode 100644 index 29810cf1fd9..00000000000 Binary files a/src/main/resources/images/address_book_32.png and /dev/null differ diff --git a/src/main/resources/images/customer_icon.png b/src/main/resources/images/customer_icon.png new file mode 100644 index 00000000000..e87bac6bcbf Binary files /dev/null and b/src/main/resources/images/customer_icon.png differ diff --git a/src/main/resources/images/home_icon.png b/src/main/resources/images/home_icon.png new file mode 100644 index 00000000000..80dbafcb777 Binary files /dev/null and b/src/main/resources/images/home_icon.png differ diff --git a/src/main/resources/images/inventory_system_logo.png b/src/main/resources/images/inventory_system_logo.png new file mode 100644 index 00000000000..11e8d212f9a Binary files /dev/null and b/src/main/resources/images/inventory_system_logo.png differ diff --git a/src/main/resources/images/logo.png b/src/main/resources/images/logo.png new file mode 100644 index 00000000000..16d0237723b Binary files /dev/null and b/src/main/resources/images/logo.png differ diff --git a/src/main/resources/images/product_icon.png b/src/main/resources/images/product_icon.png new file mode 100644 index 00000000000..72b777c69ab Binary files /dev/null and b/src/main/resources/images/product_icon.png differ diff --git a/src/main/resources/images/statistics_icon.png b/src/main/resources/images/statistics_icon.png new file mode 100644 index 00000000000..8adbbd2c3eb Binary files /dev/null and b/src/main/resources/images/statistics_icon.png differ diff --git a/src/main/resources/images/stats-icon.png b/src/main/resources/images/stats-icon.png new file mode 100644 index 00000000000..49cd0ff2bbb Binary files /dev/null and b/src/main/resources/images/stats-icon.png differ diff --git a/src/main/resources/images/transaction_icon.png b/src/main/resources/images/transaction_icon.png new file mode 100644 index 00000000000..f0516e2e0c5 Binary files /dev/null and b/src/main/resources/images/transaction_icon.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..489002340ba 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -4,6 +4,6 @@ - + diff --git a/src/main/resources/view/CustomerListCard.fxml b/src/main/resources/view/CustomerListCard.fxml new file mode 100644 index 00000000000..5010424b8d9 --- /dev/null +++ b/src/main/resources/view/CustomerListCard.fxml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/CustomerListPanel.fxml b/src/main/resources/view/CustomerListPanel.fxml new file mode 100644 index 00000000000..3b99ccd047a --- /dev/null +++ b/src/main/resources/view/CustomerListPanel.fxml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..e741043745b 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,12 +1,22 @@ +* { + -light-tab-color: #3e3f40; + -dark-tab-color: #242424; + -product-card-color: #1fcbff; + -customer-card-color: #17ebe1; + -transaction-card-color: #E797FD; + -dark-background-color: #101010; + -stack-pane-color: #008085; +} + .background { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-dark-background-color, 20%); background-color: #383838; /* Used in the default.html file */ } .label { -fx-font-size: 11pt; -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: #555555; + -fx-text-fill: #e6f3f5; -fx-opacity: 0.9; } @@ -29,20 +39,54 @@ -fx-font-family: "Segoe UI Semibold"; } +.tab { + -fx-background-color: transparent, tab-border-color, tab-color; + tab-color: linear-gradient(-light-tab-color, -dark-tab-color); + tab-border-color : transparent; + -fx-background-radius: 10px; + -fx-padding: 10 20 10 20; +} + +.tab-pane:focused > .tab-header-area > .headers-region > .tab:selected .focus-indicator { + -fx-border-color: transparent; +} + +.tab:hover { + tab-color: -light-tab-color; +} + +.tab:pressed { + tab-color: -dark-tab-color; +} + +.tab-label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-font-size: 15px; +} + +/*Solution adapted from +https://stackoverflow.com/questions/36779642/javafx-css-styling-change-highlight-color-of-selected-tabs*/ + .tab-pane { - -fx-padding: 0 0 0 1; + -fx-padding: 0 0 0 0; +} + +.tab-pane *.tab-header-background { + -fx-background-color: linear-gradient(to right, #3eed98 0%, #17ebe1 50%, #05bdf5 100%); + -fx-effect: innershadow(two-pass-box , rgba(0,0,0,0.6) , 4, 0.0 , 0 , 0); } .tab-pane .tab-header-area { - -fx-padding: 0 0 0 0; + -fx-padding: 5 10 5 10; -fx-min-height: 0; -fx-max-height: 0; } .table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; + -fx-base: -dark-background-color; + -fx-control-inner-background: -dark-background-color; + -fx-background-color: -dark-background-color; -fx-table-cell-border-color: transparent; -fx-table-header-border-color: transparent; -fx-padding: 5; @@ -65,7 +109,6 @@ } .table-view .column-header .label { - -fx-font-size: 20pt; -fx-font-family: "Segoe UI Light"; -fx-text-fill: white; -fx-alignment: center-left; @@ -73,30 +116,36 @@ } .table-view:focused .table-row-cell:filled:focused:selected { - -fx-background-color: -fx-focus-color; + -fx-background-color: #05bdf5; +} + +.separator *.line { + -fx-border-style: solid; + -fx-border-width: 1px; + -fx-border-color: -stack-pane-color; } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-dark-background-color, 20%); -fx-border-color: transparent transparent transparent #4d4d4d; } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-dark-background-color, 20%); } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: -dark-background-color; } .list-cell { -fx-label-padding: 0 0 0 0; -fx-graphic-text-gap : 0; - -fx-padding: 0 0 0 0; + -fx-padding: 0 0 10px 0; } .list-cell:filled:even { @@ -117,33 +166,35 @@ } .list-cell .label { - -fx-text-fill: white; + -fx-text-fill: #1e2730; } .cell_big_label { -fx-font-family: "Segoe UI Semibold"; -fx-font-size: 16px; - -fx-text-fill: #010504; + -fx-text-fill: black !important; } .cell_small_label { -fx-font-family: "Segoe UI"; -fx-font-size: 13px; - -fx-text-fill: #010504; + -fx-text-fill: #fafcfc; } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-stack-pane-color, 20%); + -fx-background-radius: 10px; + -fx-opacity: 0.7 } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); - -fx-border-top-width: 1px; + -fx-background-color: derive(#111111, 20%); + -fx-border-color: derive(#111111, 10%); + -fx-border-top-width: 3px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(#023448, 30%); } .result-display { @@ -165,8 +216,8 @@ } .status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 25%); + -fx-background-color: derive(-dark-background-color, 30%); + -fx-border-color: derive(-dark-background-color, 25%); -fx-border-width: 1px; } @@ -175,17 +226,17 @@ } .grid-pane { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(-dark-background-color, 30%); + -fx-border-color: derive(-dark-background-color, 30%); -fx-border-width: 1px; } .grid-pane .stack-pane { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(#B682F5, 30%); } .context-menu { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(-dark-background-color, 50%); } .context-menu .label { @@ -193,7 +244,7 @@ } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#111111, 20%); } .menu-bar .label { @@ -257,11 +308,11 @@ } .dialog-pane { - -fx-background-color: #1d1d1d; + -fx-background-color: -dark-background-color; } .dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; + -fx-background-color: -dark-background-color; } .dialog-pane > *.label.content { @@ -271,7 +322,7 @@ } .dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); + -fx-background-color: derive(-dark-background-color, 25%); } .dialog-pane:header *.header-panel *.label { @@ -282,11 +333,11 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-dark-background-color, 20%); } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(-dark-background-color, 50%); -fx-background-insets: 3; } @@ -307,25 +358,44 @@ -fx-padding: 8 1 8 1; } -#cardPane { - -fx-background-color: transparent; +#cardPaneProduct { + -fx-background-color: -product-card-color; -fx-border-width: 0; + -fx-background-radius: 10px; +} + +#cardPaneCustomer { + -fx-background-color: -customer-card-color; + -fx-border-width: 0; + -fx-background-radius: 10px; +} + +#cardPaneTransaction { + -fx-background-color: -transaction-card-color; + -fx-border-width: 0; + -fx-background-radius: 10px; +} + +#cardPaneStatistics { + -fx-background-color: #1fcbff; + -fx-border-width: 0; + -fx-background-radius: 10px; } #commandTypeLabel { -fx-font-size: 11px; - -fx-text-fill: #F70D1A; + -fx-text-fill: #fad7d9; } #commandTextField { -fx-background-color: transparent #383838 transparent #383838; -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; + -fx-border-color: #383838 #383838 #383838 #383838; -fx-border-insets: 0; -fx-border-width: 1; -fx-font-family: "Segoe UI Light"; -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-text-fill: #ffffff; } #filterField, #personListPanel, #personWebpage { @@ -343,8 +413,8 @@ } #tags .label { - -fx-text-fill: white; - -fx-background-color: #3e7b91; + -fx-text-fill: #f2fcfc; + -fx-background-color: #129694; -fx-padding: 1 3 1 3; -fx-border-radius: 2; -fx-background-radius: 2; diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964..fd0eb11ccaa 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -1,11 +1,11 @@ .error { - -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */ + -fx-text-fill: #ffa1aa !important; /* The error class should always override the default text-fill style */ } .list-cell:empty { /* Empty cells will not have alternating colours */ - -fx-background: #383838; + -fx-background: derive(#101010, 20%); } .tag-selector { diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css new file mode 100644 index 00000000000..cdd9dd4dff8 --- /dev/null +++ b/src/main/resources/view/HelpWindow.css @@ -0,0 +1,23 @@ +.root { + -fx-background-color: #242424; +} + +.web-view .scroll-bar { + -fx-background-color: #242424; +} + +.web-view .increment-button:hover { + -fx-background-color: gray; +} + +.web-view .decrement-button:hover { + -fx-background-color: gray; +} + +.web-view .thumb { + -fx-background-color: gray; +} + +.web-view .thumb:hover { + -fx-background-color: dimgray; +} diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index fa0fb54d9f4..4a27338d81d 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -1,39 +1,25 @@ - - - - + - + + + - - - - - - - - - - - - - + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..84bd0857ed7 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -3,18 +3,18 @@ - - - - + + + + + - + - + @@ -24,35 +24,90 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - - - - - - - diff --git a/src/main/resources/view/NotificationWindow.fxml b/src/main/resources/view/NotificationWindow.fxml new file mode 100644 index 00000000000..f35a9a3800c --- /dev/null +++ b/src/main/resources/view/NotificationWindow.fxml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml deleted file mode 100644 index f08ea32ad55..00000000000 --- a/src/main/resources/view/PersonListCard.fxml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml deleted file mode 100644 index 8836d323cc5..00000000000 --- a/src/main/resources/view/PersonListPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/PlotWindow.fxml b/src/main/resources/view/PlotWindow.fxml new file mode 100644 index 00000000000..cf7c4f0133c --- /dev/null +++ b/src/main/resources/view/PlotWindow.fxml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ProductListCard.fxml b/src/main/resources/view/ProductListCard.fxml new file mode 100644 index 00000000000..9b33f468c66 --- /dev/null +++ b/src/main/resources/view/ProductListCard.fxml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ProductListPanel.fxml b/src/main/resources/view/ProductListPanel.fxml new file mode 100644 index 00000000000..d2bd17a4666 --- /dev/null +++ b/src/main/resources/view/ProductListPanel.fxml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/src/main/resources/view/Progress.css b/src/main/resources/view/Progress.css new file mode 100644 index 00000000000..09270978f29 --- /dev/null +++ b/src/main/resources/view/Progress.css @@ -0,0 +1,15 @@ +.root { -fx-background-color: cornsilk; -fx-padding: 15; } + +.progress-bar { +-fx-box-border: black; +-fx-control-inner-background: #383838; +} + +.progress-bar .bar { +-fx-padding: 8px; +} + +.green-bar { -fx-accent: green; } +.yellow-bar { -fx-accent: yellow; } +.orange-bar { -fx-accent: orange; } +.red-bar { -fx-accent: red; } diff --git a/src/main/resources/view/StatisticsListCard.fxml b/src/main/resources/view/StatisticsListCard.fxml new file mode 100644 index 00000000000..3a2f61e5dcc --- /dev/null +++ b/src/main/resources/view/StatisticsListCard.fxml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/StatisticsListPanel.css b/src/main/resources/view/StatisticsListPanel.css new file mode 100644 index 00000000000..8917651f50c --- /dev/null +++ b/src/main/resources/view/StatisticsListPanel.css @@ -0,0 +1,39 @@ +.list-view { + -fx-background-color: #242424; +} + +.list-view .list-cell:hover { + -fx-background-color: dimgray; +} + +.list-view .list-cell:selected { + -fx-background-color: gray; +} + +.list-view .list-cell { + -fx-background-color: #242424; +} + +.list-view .label { + -fx-text-fill: black; +} + +.list-view .scroll-bar { + -fx-background-color: #242424; +} + +.list-view .increment-button:hover { + -fx-background-color: gray; +} + +.list-view .decrement-button:hover { + -fx-background-color: gray; +} + +.list-view .thumb { + -fx-background-color: gray; +} + +.list-view .thumb:hover { + -fx-background-color: dimgray; +} diff --git a/src/main/resources/view/StatisticsListPanel.fxml b/src/main/resources/view/StatisticsListPanel.fxml new file mode 100644 index 00000000000..1cf8bdd1983 --- /dev/null +++ b/src/main/resources/view/StatisticsListPanel.fxml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TransactionListCard.fxml b/src/main/resources/view/TransactionListCard.fxml new file mode 100644 index 00000000000..8250d512cb0 --- /dev/null +++ b/src/main/resources/view/TransactionListCard.fxml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TransactionListPanel.fxml b/src/main/resources/view/TransactionListPanel.fxml new file mode 100644 index 00000000000..f37e4a9deb9 --- /dev/null +++ b/src/main/resources/view/TransactionListPanel.fxml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/fonts/Comfortaa-Bold.ttf b/src/main/resources/view/fonts/Comfortaa-Bold.ttf new file mode 100644 index 00000000000..ec3eadf0185 Binary files /dev/null and b/src/main/resources/view/fonts/Comfortaa-Bold.ttf differ diff --git a/src/main/resources/view/fonts/Comfortaa-Light.ttf b/src/main/resources/view/fonts/Comfortaa-Light.ttf new file mode 100644 index 00000000000..dea4ceca3b2 Binary files /dev/null and b/src/main/resources/view/fonts/Comfortaa-Light.ttf differ diff --git a/src/main/resources/view/fonts/Comfortaa-Regular.ttf b/src/main/resources/view/fonts/Comfortaa-Regular.ttf new file mode 100644 index 00000000000..5e814914628 Binary files /dev/null and b/src/main/resources/view/fonts/Comfortaa-Regular.ttf differ diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonInventorySystemStorageTest/invalidAndValidPersonAddressBook.json similarity index 100% rename from src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json rename to src/test/data/JsonInventorySystemStorageTest/invalidAndValidPersonAddressBook.json diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonInventorySystemStorageTest/invalidPersonAddressBook.json similarity index 100% rename from src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json rename to src/test/data/JsonInventorySystemStorageTest/invalidPersonAddressBook.json diff --git a/src/test/data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json b/src/test/data/JsonInventorySystemStorageTest/notJsonFormatAddressBook.json similarity index 100% rename from src/test/data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json rename to src/test/data/JsonInventorySystemStorageTest/notJsonFormatAddressBook.json diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json deleted file mode 100644 index 48831cc7674..00000000000 --- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "persons": [ { - "name": "Alice Pauline", - "phone": "94351253", - "email": "alice@example.com", - "address": "123, Jurong West Ave 6, #08-111", - "tagged": [ "friends" ] - }, { - "name": "Alice Pauline", - "phone": "94351253", - "email": "pauline@example.com", - "address": "4th street" - } ] -} diff --git a/src/test/data/JsonSerializableInventorySystemTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableInventorySystemTest/duplicatePersonAddressBook.json new file mode 100644 index 00000000000..683b4cabd50 --- /dev/null +++ b/src/test/data/JsonSerializableInventorySystemTest/duplicatePersonAddressBook.json @@ -0,0 +1,19 @@ +{ + "persons": [ { + "id": "a4365691-ea10-47ad-b33b-fc038f1e5e81", + "name" : "Jane", + "phone": "92763578", + "email" : "jane@gmail.com", + "address" : "Blk 26 Jurong West St 82", + "tagged" : [] + }, { + "id": "a4365691-ea10-47ad-b33b-fc038f1e5e81", + "name" : "Jane", + "phone": "92763578", + "email" : "jane@gmail.com", + "address" : "Blk 26 Jurong West St 82", + "tagged" : [] + } ], + "transactions": [], + "products": [] +} diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableInventorySystemTest/invalidPersonAddressBook.json similarity index 73% rename from src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json rename to src/test/data/JsonSerializableInventorySystemTest/invalidPersonAddressBook.json index ad3f135ae42..0174c419afd 100644 --- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonSerializableInventorySystemTest/invalidPersonAddressBook.json @@ -4,5 +4,7 @@ "phone": "9482424", "email": "invalid@email!3e", "address": "4th street" - } ] + } ], + "transactions" : [], + "products": [] } diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableInventorySystemTest/typicalPersonsAddressBook.json similarity index 67% rename from src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json rename to src/test/data/JsonSerializableInventorySystemTest/typicalPersonsAddressBook.json index f10eddee12e..b49a2e8fe55 100644 --- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json +++ b/src/test/data/JsonSerializableInventorySystemTest/typicalPersonsAddressBook.json @@ -1,46 +1,55 @@ { - "_comment": "AddressBook save file which contains the same Person values as in TypicalPersons#getTypicalAddressBook()", + "_comment": "InventorySystem save file which contains the same Person values as in TypicalPersons#getTypicalInventorySystem()", "persons" : [ { + "id": "a4365691-ea10-47ad-b33b-fc038f1e5e81", "name" : "Alice Pauline", "phone" : "94351253", "email" : "alice@example.com", "address" : "123, Jurong West Ave 6, #08-111", "tagged" : [ "friends" ] }, { + "id": "a4365692-ea10-47ad-b33b-fc038f1e5e81", "name" : "Benson Meier", "phone" : "98765432", "email" : "johnd@example.com", "address" : "311, Clementi Ave 2, #02-25", "tagged" : [ "owesMoney", "friends" ] }, { + "id": "a4365693-ea10-47ad-b33b-fc038f1e5e81", "name" : "Carl Kurz", "phone" : "95352563", "email" : "heinz@example.com", "address" : "wall street", "tagged" : [ ] }, { + "id": "a4365694-ea10-47ad-b33b-fc038f1e5e81", "name" : "Daniel Meier", "phone" : "87652533", "email" : "cornelia@example.com", "address" : "10th street", "tagged" : [ "friends" ] }, { + "id": "a4365695-ea10-47ad-b33b-fc038f1e5e81", "name" : "Elle Meyer", "phone" : "9482224", "email" : "werner@example.com", "address" : "michegan ave", "tagged" : [ ] }, { + "id": "a4365696-ea10-47ad-b33b-fc038f1e5e81", "name" : "Fiona Kunz", "phone" : "9482427", "email" : "lydia@example.com", "address" : "little tokyo", "tagged" : [ ] }, { + "id": "a4365697-ea10-47ad-b33b-fc038f1e5e81", "name" : "George Best", "phone" : "9482442", "email" : "anna@example.com", "address" : "4th street", "tagged" : [ ] - } ] + } ], + "transactions" : [], + "products": [] } diff --git a/src/test/java/seedu/address/commons/util/AppUtilTest.java b/src/test/java/seedu/address/commons/util/AppUtilTest.java index 594de1e6365..afe0671afa9 100644 --- a/src/test/java/seedu/address/commons/util/AppUtilTest.java +++ b/src/test/java/seedu/address/commons/util/AppUtilTest.java @@ -9,7 +9,7 @@ public class AppUtilTest { @Test public void getImage_exitingImage() { - assertNotNull(AppUtil.getImage("/images/address_book_32.png")); + assertNotNull(AppUtil.getImage("/images/inventory_system_logo.png")); } @Test diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index ad923ac249a..3f83739d6ab 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -8,7 +8,7 @@ import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.AMY; +import static seedu.address.testutil.customer.TypicalCustomers.AMY; import java.io.IOException; import java.nio.file.Path; @@ -17,20 +17,20 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.customer.AddCustomerCommand; +import seedu.address.logic.commands.customer.ListCustomerCommand; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyInventorySystem; import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.storage.JsonAddressBookStorage; +import seedu.address.model.customer.Customer; +import seedu.address.storage.JsonInventorySystemStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.StorageManager; -import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.customer.CustomerBuilder; public class LogicManagerTest { private static final IOException DUMMY_IO_EXCEPTION = new IOException("dummy exception"); @@ -43,8 +43,8 @@ public class LogicManagerTest { @BeforeEach public void setUp() { - JsonAddressBookStorage addressBookStorage = - new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json")); + JsonInventorySystemStorage addressBookStorage = + new JsonInventorySystemStorage(temporaryFolder.resolve("addressBook.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json")); StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage); logic = new LogicManager(model, storage); @@ -58,20 +58,20 @@ public void execute_invalidCommandFormat_throwsParseException() { @Test public void execute_commandExecutionError_throwsCommandException() { - String deleteCommand = "delete 9"; + String deleteCommand = "deletec 9"; assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } @Test public void execute_validCommand_success() throws Exception { - String listCommand = ListCommand.COMMAND_WORD; - assertCommandSuccess(listCommand, ListCommand.MESSAGE_SUCCESS, model); + String listCommand = ListCustomerCommand.COMMAND_WORD; + assertCommandSuccess(listCommand, ListCustomerCommand.MESSAGE_EMPTY, model); } @Test public void execute_storageThrowsIoException_throwsCommandException() { // Setup LogicManager with JsonAddressBookIoExceptionThrowingStub - JsonAddressBookStorage addressBookStorage = + JsonInventorySystemStorage addressBookStorage = new JsonAddressBookIoExceptionThrowingStub(temporaryFolder.resolve("ioExceptionAddressBook.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("ioExceptionUserPrefs.json")); @@ -79,18 +79,18 @@ public void execute_storageThrowsIoException_throwsCommandException() { logic = new LogicManager(model, storage); // Execute add command - String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + String addCommand = AddCustomerCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; - Person expectedPerson = new PersonBuilder(AMY).withTags().build(); + Customer expectedCustomer = new CustomerBuilder(AMY).withTags().build(); ModelManager expectedModel = new ModelManager(); - expectedModel.addPerson(expectedPerson); + expectedModel.addPerson(expectedCustomer); String expectedMessage = LogicManager.FILE_OPS_ERROR_MESSAGE + DUMMY_IO_EXCEPTION; assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel); } @Test public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPersonList().remove(0)); + assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredCustomerList().remove(0)); } /** @@ -129,7 +129,7 @@ private void assertCommandException(String inputCommand, String expectedMessage) */ private void assertCommandFailure(String inputCommand, Class expectedException, String expectedMessage) { - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getInventorySystem(), new UserPrefs()); assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel); } @@ -149,13 +149,13 @@ private void assertCommandFailure(String inputCommand, Class new AddCommand(null)); - } - - @Test - public void execute_personAcceptedByModel_addSuccessful() throws Exception { - ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); - Person validPerson = new PersonBuilder().build(); - - CommandResult commandResult = new AddCommand(validPerson).execute(modelStub); - - assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validPerson), commandResult.getFeedbackToUser()); - assertEquals(Arrays.asList(validPerson), modelStub.personsAdded); - } - - @Test - public void execute_duplicatePerson_throwsCommandException() { - Person validPerson = new PersonBuilder().build(); - AddCommand addCommand = new AddCommand(validPerson); - ModelStub modelStub = new ModelStubWithPerson(validPerson); - - assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub)); - } - - @Test - public void equals() { - Person alice = new PersonBuilder().withName("Alice").build(); - Person bob = new PersonBuilder().withName("Bob").build(); - AddCommand addAliceCommand = new AddCommand(alice); - AddCommand addBobCommand = new AddCommand(bob); - - // same object -> returns true - assertTrue(addAliceCommand.equals(addAliceCommand)); - - // same values -> returns true - AddCommand addAliceCommandCopy = new AddCommand(alice); - assertTrue(addAliceCommand.equals(addAliceCommandCopy)); - - // different types -> returns false - assertFalse(addAliceCommand.equals(1)); - - // null -> returns false - assertFalse(addAliceCommand.equals(null)); - - // different person -> returns false - assertFalse(addAliceCommand.equals(addBobCommand)); - } - - /** - * A default model stub that have all of the methods failing. - */ - private class ModelStub implements Model { - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - throw new AssertionError("This method should not be called."); - } - - @Override - public GuiSettings getGuiSettings() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - throw new AssertionError("This method should not be called."); - } - - @Override - public Path getAddressBookFilePath() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void addPerson(Person person) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setAddressBook(ReadOnlyAddressBook newData) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - throw new AssertionError("This method should not be called."); - } - - @Override - public boolean hasPerson(Person person) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void deletePerson(Person target) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ObservableList getFilteredPersonList() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - throw new AssertionError("This method should not be called."); - } - } - - /** - * A Model stub that contains a single person. - */ - private class ModelStubWithPerson extends ModelStub { - private final Person person; - - ModelStubWithPerson(Person person) { - requireNonNull(person); - this.person = person; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return this.person.isSamePerson(person); - } - } - - /** - * A Model stub that always accept the person being added. - */ - private class ModelStubAcceptingPersonAdded extends ModelStub { - final ArrayList personsAdded = new ArrayList<>(); - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return personsAdded.stream().anyMatch(person::isSamePerson); - } - - @Override - public void addPerson(Person person) { - requireNonNull(person); - personsAdded.add(person); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return new AddressBook(); - } - } - -} diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java deleted file mode 100644 index 80d9110c03a..00000000000 --- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package seedu.address.logic.commands; - -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.Test; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; - -public class ClearCommandTest { - - @Test - public void execute_emptyAddressBook_success() { - Model model = new ModelManager(); - Model expectedModel = new ModelManager(); - - assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); - } - - @Test - public void execute_nonEmptyAddressBook_success() { - Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - expectedModel.setAddressBook(new AddressBook()); - - assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/commands/CommandResultTest.java index 4f3eb46e9ef..0104f742dd1 100644 --- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java +++ b/src/test/java/seedu/address/logic/commands/CommandResultTest.java @@ -14,7 +14,14 @@ public void equals() { // same values -> returns true assertTrue(commandResult.equals(new CommandResult("feedback"))); - assertTrue(commandResult.equals(new CommandResult("feedback", false, false))); + assertTrue(commandResult.equals(new CommandResult("feedback", + null, + null, + "", + false, + false, + false, + false))); // same object -> returns true assertTrue(commandResult.equals(commandResult)); @@ -29,10 +36,24 @@ public void equals() { assertFalse(commandResult.equals(new CommandResult("different"))); // different showHelp value -> returns false - assertFalse(commandResult.equals(new CommandResult("feedback", true, false))); + assertFalse(commandResult.equals(new CommandResult("feedback", + null, + null, + "", + true, + false, + false, + false))); // different exit value -> returns false - assertFalse(commandResult.equals(new CommandResult("feedback", false, true))); + assertFalse(commandResult.equals(new CommandResult("feedback", + null, + null, + "", + false, + false, + false, + true))); } @Test @@ -46,9 +67,23 @@ public void hashcode() { assertNotEquals(commandResult.hashCode(), new CommandResult("different").hashCode()); // different showHelp value -> returns different hashcode - assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false).hashCode()); + assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", + null, + null, + "", + true, + false, + false, + false).hashCode()); // different exit value -> returns different hashcode - assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true).hashCode()); + assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", + null, + null, + "", + false, + false, + false, + true).hashCode()); } } diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 643a1d08069..4c5ddae9361 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -3,10 +3,20 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COSTPRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MONEY; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRODUCT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_QUANTITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALES; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_THRESHOLD; import static seedu.address.testutil.Assert.assertThrows; import java.util.ArrayList; @@ -14,12 +24,20 @@ import java.util.List; import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.customer.EditCustomerCommand; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; +import seedu.address.logic.commands.product.EditProductCommand; +import seedu.address.model.InventorySystem; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.NameContainsKeywordsPredicate; +import seedu.address.model.product.DescriptionContainsKeywordsPredicate; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.CustomerContainsKeywordPredicate; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.util.QuantityThreshold; +import seedu.address.testutil.customer.EditCustomerDescriptorBuilder; +import seedu.address.testutil.product.EditProductDescriptorBuilder; /** * Contains helper methods for testing commands. @@ -37,6 +55,31 @@ public class CommandTestUtil { public static final String VALID_TAG_HUSBAND = "husband"; public static final String VALID_TAG_FRIEND = "friend"; + public static final String VALID_DESCRIPTION_WATCH = "Black watch"; + public static final String VALID_DESCRIPTION_BAG = "Black bag"; + public static final String VALID_COSTPRICE_WATCH = "249"; + public static final String VALID_COSTPRICE_BAG = "149"; + public static final String VALID_PRICE_WATCH = "11"; + public static final String VALID_PRICE_BAG = "22"; + public static final String VALID_QUANTITY_WATCH = "1"; + public static final String VALID_QUANTITY_BAG = "2"; + public static final String VALID_SALES_WATCH = "22"; + public static final String VALID_SALES_BAG = "44"; + public static final String VALID_THRESHOLD_WATCH = "20"; + public static final String VALID_THRESHOLD_BAG = "12"; + + public static final String VALID_CUSTOMER_INDEX_AMY = "1"; + public static final String VALID_PRODUCT_INDEX_BAG = "1"; + public static final String VALID_DATETIME_AMY_BAG = "2020-03-01 10:00"; + public static final String VALID_QUANTITY_AMY_BAG = "1"; + public static final String VALID_MONEY_AMY_BAG = "30"; + public static final String VALID_DESCRIPTION_AMY_BAG = "under discount"; + + public static final String INVALID_CUSTOMER_INDEX_AMY = "-1"; + public static final String INVALID_PRODUCT_INDEX_BAG = "0"; + public static final String INVALID_DATETIME_AMY_BAG = "2020-03-0110:00"; + public static final String INVALID_MONEY_AMY_BAG = "-30"; + public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; public static final String PHONE_DESC_AMY = " " + PREFIX_PHONE + VALID_PHONE_AMY; @@ -48,25 +91,75 @@ public class CommandTestUtil { public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND; public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND; + public static final String DESCRIPTION_DESC_BAG = " " + PREFIX_DESCRIPTION + VALID_DESCRIPTION_BAG; + public static final String DESCRIPTION_DESC_WATCH = " " + PREFIX_DESCRIPTION + VALID_DESCRIPTION_WATCH; + public static final String COSTPRICE_DESC_BAG = " " + PREFIX_COSTPRICE + VALID_COSTPRICE_BAG; + public static final String COSTPRICE_DESC_WATCH = " " + PREFIX_COSTPRICE + VALID_COSTPRICE_WATCH; + public static final String PRICE_DESC_BAG = " " + PREFIX_PRICE + VALID_PRICE_BAG; + public static final String PRICE_DESC_WATCH = " " + PREFIX_PRICE + VALID_PRICE_WATCH; + public static final String QUANTITY_DESC_BAG = " " + PREFIX_QUANTITY + VALID_QUANTITY_BAG; + public static final String QUANTITY_DESC_WATCH = " " + PREFIX_QUANTITY + VALID_QUANTITY_WATCH; + public static final String SALES_DESC_BAG = " " + PREFIX_SALES + VALID_SALES_BAG; + public static final String SALES_DESC_WATCH = " " + PREFIX_SALES + VALID_SALES_WATCH; + public static final String THRESHOLD_DESC_BAG = " " + PREFIX_THRESHOLD + VALID_THRESHOLD_BAG; + public static final String THRESHOLD_DESC_WATCH = " " + PREFIX_THRESHOLD + VALID_THRESHOLD_WATCH; + + public static final String CUSTOMER_INDEX_DESC_AMY_BAG = " " + PREFIX_CUSTOMER + VALID_CUSTOMER_INDEX_AMY; + public static final String PRODUCT_INDEX_DESC_AMY_BAG = " " + PREFIX_PRODUCT + VALID_PRODUCT_INDEX_BAG; + public static final String DATETIME_DESC_AMY_BAG = " " + PREFIX_DATETIME + VALID_DATETIME_AMY_BAG; + public static final String QUANTITY_DESC_AMY_BAG = " " + PREFIX_QUANTITY + VALID_QUANTITY_AMY_BAG; + public static final String MONEY_DESC_AMY_BAG = " " + PREFIX_MONEY + VALID_MONEY_AMY_BAG; + public static final String DESCRIPTION_DESC_AMY_BAG = " " + PREFIX_DESCRIPTION + VALID_DESCRIPTION_AMY_BAG; + + public static final String CUSTOMER_NAME_DESC_AMY = " " + PREFIX_CUSTOMER + VALID_NAME_AMY; + public static final String PRODUCT_DESCRIPTION_DESC_BAG = " " + PREFIX_PRODUCT + VALID_DESCRIPTION_BAG; + public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags + public static final String INVALID_DESCRIPTION_DESC = " " + PREFIX_DESCRIPTION + ""; + public static final String INVALID_PRICE_DESC = " " + PREFIX_PRICE + "911a"; + public static final String INVALID_QUANTITY_DESC = " " + PREFIX_QUANTITY + "bob!yahoo"; + public static final String INVALID_SALES_DESC = " " + PREFIX_SALES; + + public static final String INVALID_CUSTOMER_INDEX_DESC = " " + PREFIX_CUSTOMER + INVALID_CUSTOMER_INDEX_AMY; + public static final String INVALID_PRODUCT_INDEX_DESC = " " + PREFIX_PRODUCT + INVALID_PRODUCT_INDEX_BAG; + public static final String INVALID_DATETIME_DESC = " " + PREFIX_DATETIME + INVALID_DATETIME_AMY_BAG; + public static final String INVALID_MONEY_DESC = " " + PREFIX_MONEY + INVALID_MONEY_AMY_BAG; + + public static final String INVALID_CUSTOMER_NAME_DESC = " " + PREFIX_CUSTOMER + "James&"; + public static final String INVALID_PRODUCT_DESCRIPTION_DESC = " " + PREFIX_PRODUCT + ""; + public static final String PREAMBLE_WHITESPACE = "\t \r \n"; public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; - public static final EditCommand.EditPersonDescriptor DESC_AMY; - public static final EditCommand.EditPersonDescriptor DESC_BOB; + public static final EditCustomerCommand.EditCustomerDescriptor DESC_AMY; + public static final EditCustomerCommand.EditCustomerDescriptor DESC_BOB; + + public static final EditProductCommand.EditProductDescriptor DESC_BAG; + public static final EditProductCommand.EditProductDescriptor DESC_WATCH; + + public static final QuantityThreshold THRESHOLD_BAG; + public static final QuantityThreshold THRESHOLD_WATCH; static { - DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) + DESC_AMY = new EditCustomerDescriptorBuilder().withName(VALID_NAME_AMY) .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) .withTags(VALID_TAG_FRIEND).build(); - DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) + DESC_BOB = new EditCustomerDescriptorBuilder().withName(VALID_NAME_BOB) .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); + DESC_BAG = new EditProductDescriptorBuilder().withDescription(VALID_DESCRIPTION_BAG) + .withCostPrice(VALID_COSTPRICE_BAG).withPrice(VALID_PRICE_BAG).withQuantity(VALID_QUANTITY_BAG) + .withSales(VALID_SALES_BAG).withThreshold(VALID_THRESHOLD_BAG).build(); + DESC_WATCH = new EditProductDescriptorBuilder().withDescription(VALID_DESCRIPTION_WATCH) + .withCostPrice(VALID_COSTPRICE_WATCH).withPrice(VALID_PRICE_WATCH).withQuantity(VALID_QUANTITY_WATCH) + .withSales(VALID_SALES_WATCH).withThreshold(VALID_THRESHOLD_WATCH).build(); + THRESHOLD_BAG = new QuantityThreshold(VALID_THRESHOLD_BAG); + THRESHOLD_WATCH = new QuantityThreshold(VALID_THRESHOLD_WATCH); } /** @@ -99,30 +192,56 @@ public static void assertCommandSuccess(Command command, Model actualModel, Stri * Executes the given {@code command}, confirms that
* - a {@code CommandException} is thrown
* - the CommandException message matches {@code expectedMessage}
- * - the address book, filtered person list and selected person in {@code actualModel} remain unchanged + * - the address book, filtered customer list and selected customer in {@code actualModel} remain unchanged */ public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) { // we are unable to defensively copy the model for comparison later, so we can // only do so by copying its components. - AddressBook expectedAddressBook = new AddressBook(actualModel.getAddressBook()); - List expectedFilteredList = new ArrayList<>(actualModel.getFilteredPersonList()); + InventorySystem expectedAddressBook = new InventorySystem(actualModel.getInventorySystem()); + List expectedFilteredList = new ArrayList<>(actualModel.getFilteredCustomerList()); assertThrows(CommandException.class, expectedMessage, () -> command.execute(actualModel)); - assertEquals(expectedAddressBook, actualModel.getAddressBook()); - assertEquals(expectedFilteredList, actualModel.getFilteredPersonList()); + assertEquals(expectedAddressBook, actualModel.getInventorySystem()); + assertEquals(expectedFilteredList, actualModel.getFilteredCustomerList()); } /** - * Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in the + * Updates {@code model}'s filtered list to show only the customer at the given {@code targetIndex} in the * {@code model}'s address book. */ public static void showPersonAtIndex(Model model, Index targetIndex) { - assertTrue(targetIndex.getZeroBased() < model.getFilteredPersonList().size()); + assertTrue(targetIndex.getZeroBased() < model.getFilteredCustomerList().size()); + + Customer customer = model.getFilteredCustomerList().get(targetIndex.getZeroBased()); + final String[] splitName = customer.getName().fullName.split("\\s+"); + model.updateFilteredCustomerList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0]))); + + assertEquals(1, model.getFilteredCustomerList().size()); + } - Person person = model.getFilteredPersonList().get(targetIndex.getZeroBased()); - final String[] splitName = person.getName().fullName.split("\\s+"); - model.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0]))); + /** + * Updates {@code model}'s filtered list to show only the product at the given {@code targetIndex} in the + * {@code model}'s address book. + */ + public static void showProductAtIndex(Model model, Index targetIndex) { + assertTrue(targetIndex.getZeroBased() < model.getFilteredProductList().size()); + + Product product = model.getFilteredProductList().get(targetIndex.getZeroBased()); + final String[] splitName = product.getDescription().value.split("\\s+"); + model.updateFilteredProductList(new DescriptionContainsKeywordsPredicate(Arrays.asList(splitName[0]))); + + assertEquals(1, model.getFilteredProductList().size()); + } + + /** + * Updates {@code model}'s filtered list to show only the transaction at the given {@code targetIndex} in the + * {@code model}'s address book. + */ + public static void showTransactionAtIndex(Model model, Index targetIndex) { + assertTrue(targetIndex.getZeroBased() < model.getFilteredTransactionList().size()); - assertEquals(1, model.getFilteredPersonList().size()); + Transaction transaction = model.getFilteredTransactionList().get(targetIndex.getZeroBased()); + final String[] splitName = transaction.getCustomer().getName().toString().split("\\s+"); + model.updateFilteredTransactionList(new CustomerContainsKeywordPredicate(Arrays.asList(splitName[0]))); } } diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java deleted file mode 100644 index 0f77d8295f6..00000000000 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.Test; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; - -/** - * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for - * {@code DeleteCommand}. - */ -public class DeleteCommandTest { - - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void execute_validIndexUnfilteredList_success() { - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); - - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); - - ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - expectedModel.deletePerson(personToDelete); - - assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_invalidIndexUnfilteredList_throwsCommandException() { - Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); - DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); - - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - @Test - public void execute_validIndexFilteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); - - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); - - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - expectedModel.deletePerson(personToDelete); - showNoPerson(expectedModel); - - assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_invalidIndexFilteredList_throwsCommandException() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - Index outOfBoundIndex = INDEX_SECOND_PERSON; - // ensures that outOfBoundIndex is still in bounds of address book list - assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); - - DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); - - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - @Test - public void equals() { - DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON); - DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON); - - // same object -> returns true - assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); - - // same values -> returns true - DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON); - assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); - - // different types -> returns false - assertFalse(deleteFirstCommand.equals(1)); - - // null -> returns false - assertFalse(deleteFirstCommand.equals(null)); - - // different person -> returns false - assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); - } - - /** - * Updates {@code model}'s filtered list to show no one. - */ - private void showNoPerson(Model model) { - model.updateFilteredPersonList(p -> false); - - assertTrue(model.getFilteredPersonList().isEmpty()); - } -} diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java deleted file mode 100644 index 1c27530fa99..00000000000 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.Test; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; -import seedu.address.testutil.PersonBuilder; - -/** - * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for EditCommand. - */ -public class EditCommandTest { - - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void execute_allFieldsSpecifiedUnfilteredList_success() { - Person editedPerson = new PersonBuilder().build(); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_someFieldsSpecifiedUnfilteredList_success() { - Index indexLastPerson = Index.fromOneBased(model.getFilteredPersonList().size()); - Person lastPerson = model.getFilteredPersonList().get(indexLastPerson.getZeroBased()); - - PersonBuilder personInList = new PersonBuilder(lastPerson); - Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withTags(VALID_TAG_HUSBAND).build(); - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) - .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build(); - EditCommand editCommand = new EditCommand(indexLastPerson, descriptor); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(lastPerson, editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_noFieldSpecifiedUnfilteredList_success() { - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor()); - Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_filteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - Person editedPerson = new PersonBuilder(personInFilteredList).withName(VALID_NAME_BOB).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, - new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_duplicatePersonUnfilteredList_failure() { - Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_SECOND_PERSON, descriptor); - - assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); - } - - @Test - public void execute_duplicatePersonFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - // edit person in filtered list into a duplicate in address book - Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, - new EditPersonDescriptorBuilder(personInList).build()); - - assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); - } - - @Test - public void execute_invalidPersonIndexUnfilteredList_failure() { - Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build(); - EditCommand editCommand = new EditCommand(outOfBoundIndex, descriptor); - - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - /** - * Edit filtered list where index is larger than size of filtered list, - * but smaller than size of address book - */ - @Test - public void execute_invalidPersonIndexFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - Index outOfBoundIndex = INDEX_SECOND_PERSON; - // ensures that outOfBoundIndex is still in bounds of address book list - assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); - - EditCommand editCommand = new EditCommand(outOfBoundIndex, - new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); - - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - @Test - public void equals() { - final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY); - - // same values -> returns true - EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY); - EditCommand commandWithSameValues = new EditCommand(INDEX_FIRST_PERSON, copyDescriptor); - assertTrue(standardCommand.equals(commandWithSameValues)); - - // same object -> returns true - assertTrue(standardCommand.equals(standardCommand)); - - // null -> returns false - assertFalse(standardCommand.equals(null)); - - // different types -> returns false - assertFalse(standardCommand.equals(new ClearCommand())); - - // different index -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND_PERSON, DESC_AMY))); - - // different descriptor -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST_PERSON, DESC_BOB))); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java index 9533c473875..68fa0024970 100644 --- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java @@ -14,7 +14,14 @@ public class ExitCommandTest { @Test public void execute_exit_success() { - CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, + null, + null, + "", + false, + false, + false, + true); assertCommandSuccess(new ExitCommand(), model, expectedCommandResult, expectedModel); } } diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java deleted file mode 100644 index 9b15db28bbb..00000000000 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.CARL; -import static seedu.address.testutil.TypicalPersons.ELLE; -import static seedu.address.testutil.TypicalPersons.FIONA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.util.Arrays; -import java.util.Collections; - -import org.junit.jupiter.api.Test; - -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Contains integration tests (interaction with the Model) for {@code FindCommand}. - */ -public class FindCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void equals() { - NameContainsKeywordsPredicate firstPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("first")); - NameContainsKeywordsPredicate secondPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("second")); - - FindCommand findFirstCommand = new FindCommand(firstPredicate); - FindCommand findSecondCommand = new FindCommand(secondPredicate); - - // same object -> returns true - assertTrue(findFirstCommand.equals(findFirstCommand)); - - // same values -> returns true - FindCommand findFirstCommandCopy = new FindCommand(firstPredicate); - assertTrue(findFirstCommand.equals(findFirstCommandCopy)); - - // different types -> returns false - assertFalse(findFirstCommand.equals(1)); - - // null -> returns false - assertFalse(findFirstCommand.equals(null)); - - // different person -> returns false - assertFalse(findFirstCommand.equals(findSecondCommand)); - } - - @Test - public void execute_zeroKeywords_noPersonFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); - NameContainsKeywordsPredicate predicate = preparePredicate(" "); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredPersonList(predicate); - assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Collections.emptyList(), model.getFilteredPersonList()); - } - - @Test - public void execute_multipleKeywords_multiplePersonsFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); - NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz"); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredPersonList(predicate); - assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList()); - } - - /** - * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. - */ - private NameContainsKeywordsPredicate preparePredicate(String userInput) { - return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); - } -} diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java index 4904fc4352e..53faaac13e9 100644 --- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java @@ -14,7 +14,14 @@ public class HelpCommandTest { @Test public void execute_help_success() { - CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false); + CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, + null, + null, + "", + true, + false, + false, + false); assertCommandSuccess(new HelpCommand(), model, expectedCommandResult, expectedModel); } } diff --git a/src/test/java/seedu/address/logic/commands/customer/AddCustomerCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/customer/AddCustomerCommandIntegrationTest.java new file mode 100644 index 00000000000..5740c4004ca --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/customer/AddCustomerCommandIntegrationTest.java @@ -0,0 +1,47 @@ +package seedu.address.logic.commands.customer; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.customer.TypicalCustomers.getTypicalInventorySystem; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.customer.Customer; +import seedu.address.testutil.customer.CustomerBuilder; + +/** + * Contains integration tests (interaction with the Model) for {@code AddCustomerCommand}. + */ +public class AddCustomerCommandIntegrationTest { + + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + } + + @Test + public void execute_newPerson_success() { + Customer validCustomer = new CustomerBuilder("a4365691-ea10-47ad-b33b-fc038f1e5e80").build(); + + Model expectedModel = new ModelManager(model.getInventorySystem(), new UserPrefs()); + expectedModel.addPerson(validCustomer); + + assertCommandSuccess(new AddCustomerCommand(validCustomer), model, + String.format(AddCustomerCommand.MESSAGE_SUCCESS, validCustomer), expectedModel); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() { + Customer customerInList = model.getInventorySystem().getPersonList().get(0); + assertCommandFailure(new AddCustomerCommand(customerInList), model, + Messages.MESSAGE_DUPLICATE_PERSON); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/customer/AddCustomerCommandTest.java b/src/test/java/seedu/address/logic/commands/customer/AddCustomerCommandTest.java new file mode 100644 index 00000000000..b916f4c2359 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/customer/AddCustomerCommandTest.java @@ -0,0 +1,288 @@ +package seedu.address.logic.commands.customer; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE_ID; +import static seedu.address.testutil.customer.TypicalCustomers.BOB_ID; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.UUID; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.InventorySystem; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyInventorySystem; +import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; +import seedu.address.testutil.customer.CustomerBuilder; + +public class AddCustomerCommandTest { + + @Test + public void constructor_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new AddCustomerCommand(null)); + } + + @Test + public void execute_personAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); + Customer validCustomer = new CustomerBuilder(ALICE_ID).build(); + + CommandResult commandResult = new AddCustomerCommand(validCustomer).execute(modelStub); + + assertEquals(String.format(AddCustomerCommand.MESSAGE_SUCCESS, validCustomer), + commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(validCustomer), modelStub.personsAdded); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() { + Customer validCustomer = new CustomerBuilder(ALICE_ID).build(); + AddCustomerCommand addCustomerCommand = new AddCustomerCommand(validCustomer); + ModelStub modelStub = new ModelStubWithPerson(validCustomer); + + assertThrows(CommandException.class, + Messages.MESSAGE_DUPLICATE_PERSON, () -> addCustomerCommand.execute(modelStub)); + } + + @Test + public void equals() { + Customer alice = new CustomerBuilder(ALICE_ID).withName("Alice").build(); + Customer bob = new CustomerBuilder(BOB_ID).withName("Bob").build(); + AddCustomerCommand addAliceCommand = new AddCustomerCommand(alice); + AddCustomerCommand addBobCommand = new AddCustomerCommand(bob); + + // same object -> returns true + assertTrue(addAliceCommand.equals(addAliceCommand)); + + // same values -> returns true + AddCustomerCommand addAliceCommandCopy = new AddCustomerCommand(alice); + assertTrue(addAliceCommand.equals(addAliceCommandCopy)); + + // different types -> returns false + assertFalse(addAliceCommand.equals(1)); + + // null -> returns false + assertFalse(addAliceCommand.equals(null)); + + // different customer -> returns false + assertFalse(addAliceCommand.equals(addBobCommand)); + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getInventorySystemFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setInventorySystemFilePath(Path inventorySystemFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addPerson(Customer customer) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addProduct(Product product) { + + } + + @Override + public Product findProductById(UUID id) { + return null; + } + + @Override + public void setInventorySystem(ReadOnlyInventorySystem inventorySystem, String commandWord) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyInventorySystem getInventorySystem() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasPerson(Customer customer) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasProduct(Product product) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deletePerson(Customer target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteProduct(Product target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTransaction(Transaction target) { + + } + + @Override + public void setPerson(Customer target, Customer editedCustomer) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setProduct(Product target, Product editedProduct) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTransaction(Transaction target, Transaction editedTransaction) { + + } + + @Override + public ObservableList getFilteredCustomerList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTransaction(Transaction transaction) { + return false; + } + + @Override + public void addTransaction(Transaction transaction) { + + } + + @Override + public ObservableList filterTransaction(Predicate predicate) { + return null; + } + + @Override + public void updateFilteredCustomerList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredCustomerList() { + + } + + @Override + public void updateFilteredProductList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredProductList() { + + } + + @Override + public void updateFilteredTransactionList(Predicate predicate) { + + } + + @Override + public void updateFilteredTransactionList() { + + } + + @Override + public ObservableList getFilteredProductList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTransactionList() { + return null; + } + } + + /** + * A Model stub that contains a single customer. + */ + private class ModelStubWithPerson extends ModelStub { + private final Customer customer; + + ModelStubWithPerson(Customer customer) { + requireNonNull(customer); + this.customer = customer; + } + + @Override + public boolean hasPerson(Customer customer) { + requireNonNull(customer); + return this.customer.isSamePerson(customer); + } + } + + /** + * A Model stub that always accept the customer being added. + */ + private class ModelStubAcceptingPersonAdded extends ModelStub { + final ArrayList personsAdded = new ArrayList<>(); + + @Override + public boolean hasPerson(Customer customer) { + requireNonNull(customer); + return personsAdded.stream().anyMatch(customer::isSamePerson); + } + + @Override + public void addPerson(Customer customer) { + requireNonNull(customer); + personsAdded.add(customer); + } + + @Override + public ReadOnlyInventorySystem getInventorySystem() { + return new InventorySystem(); + } + } + +} diff --git a/src/test/java/seedu/address/logic/commands/customer/ClearCustomerCommandTest.java b/src/test/java/seedu/address/logic/commands/customer/ClearCustomerCommandTest.java new file mode 100644 index 00000000000..f98351b6d1c --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/customer/ClearCustomerCommandTest.java @@ -0,0 +1,32 @@ +package seedu.address.logic.commands.customer; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.customer.TypicalCustomers.getTypicalInventorySystem; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.InventorySystem; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +public class ClearCustomerCommandTest { + + @Test + public void execute_emptyInventorySystem_success() { + Model model = new ModelManager(); + Model expectedModel = new ModelManager(); + + assertCommandSuccess(new ClearCustomerCommand(), model, ClearCustomerCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_nonEmptyInventorySystem_success() { + Model model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + expectedModel.setInventorySystem(new InventorySystem(), ClearCustomerCommand.COMMAND_WORD); + + assertCommandSuccess(new ClearCustomerCommand(), model, ClearCustomerCommand.MESSAGE_SUCCESS, expectedModel); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/customer/DeleteCustomerCommandTest.java b/src/test/java/seedu/address/logic/commands/customer/DeleteCustomerCommandTest.java new file mode 100644 index 00000000000..3d7f294208a --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/customer/DeleteCustomerCommandTest.java @@ -0,0 +1,109 @@ +package seedu.address.logic.commands.customer; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.customer.TypicalCustomers.getTypicalInventorySystem; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.customer.Customer; + +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * {@code DeleteCustomerCommand}. + */ +public class DeleteCustomerCommandTest { + + private Model model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Customer customerToDelete = model.getFilteredCustomerList().get(INDEX_FIRST_PERSON.getZeroBased()); + DeleteCustomerCommand deleteCustomerCommand = new DeleteCustomerCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(DeleteCustomerCommand.MESSAGE_DELETE_PERSON_SUCCESS, customerToDelete); + + ModelManager expectedModel = new ModelManager(model.getInventorySystem(), new UserPrefs()); + expectedModel.deletePerson(customerToDelete); + + assertCommandSuccess(deleteCustomerCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredCustomerList().size() + 1); + DeleteCustomerCommand deleteCustomerCommand = new DeleteCustomerCommand(outOfBoundIndex); + + assertCommandFailure(deleteCustomerCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexFilteredList_success() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Customer customerToDelete = model.getFilteredCustomerList().get(INDEX_FIRST_PERSON.getZeroBased()); + DeleteCustomerCommand deleteCustomerCommand = new DeleteCustomerCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(DeleteCustomerCommand.MESSAGE_DELETE_PERSON_SUCCESS, customerToDelete); + + Model expectedModel = new ModelManager(model.getInventorySystem(), new UserPrefs()); + expectedModel.deletePerson(customerToDelete); + showNoPerson(expectedModel); + + assertCommandSuccess(deleteCustomerCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Index outOfBoundIndex = INDEX_SECOND_PERSON; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getInventorySystem().getPersonList().size()); + + DeleteCustomerCommand deleteCustomerCommand = new DeleteCustomerCommand(outOfBoundIndex); + + assertCommandFailure(deleteCustomerCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void equals() { + DeleteCustomerCommand deleteFirstCommand = new DeleteCustomerCommand(INDEX_FIRST_PERSON); + DeleteCustomerCommand deleteSecondCommand = new DeleteCustomerCommand(INDEX_SECOND_PERSON); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + DeleteCustomerCommand deleteFirstCommandCopy = new DeleteCustomerCommand(INDEX_FIRST_PERSON); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different customer -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } + + /** + * Updates {@code model}'s filtered list to show no one. + */ + private void showNoPerson(Model model) { + model.updateFilteredCustomerList(p -> false); + + assertTrue(model.getFilteredCustomerList().isEmpty()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/customer/EditCustomerCommandTest.java b/src/test/java/seedu/address/logic/commands/customer/EditCustomerCommandTest.java new file mode 100644 index 00000000000..b31317fb6db --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/customer/EditCustomerCommandTest.java @@ -0,0 +1,182 @@ +package seedu.address.logic.commands.customer; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE_ID; +import static seedu.address.testutil.customer.TypicalCustomers.getTypicalInventorySystem; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.customer.EditCustomerCommand.EditCustomerDescriptor; +import seedu.address.model.InventorySystem; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.customer.Customer; +import seedu.address.testutil.customer.CustomerBuilder; +import seedu.address.testutil.customer.EditCustomerDescriptorBuilder; + + +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests + * for EditCustomerCommand. + */ +public class EditCustomerCommandTest { + + private Model model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + + @Test + public void execute_allFieldsSpecifiedUnfilteredList_success() { + Customer editedCustomer = new CustomerBuilder(ALICE_ID).build(); + EditCustomerDescriptor descriptor = new EditCustomerDescriptorBuilder(editedCustomer).build(); + EditCustomerCommand editCustomerCommand = new EditCustomerCommand(INDEX_FIRST_PERSON, descriptor); + + String expectedMessage = String.format(EditCustomerCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedCustomer); + + Model expectedModel = new ModelManager(new InventorySystem(model.getInventorySystem()), new UserPrefs()); + expectedModel.setPerson(model.getFilteredCustomerList().get(0), editedCustomer); + + assertCommandSuccess(editCustomerCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_someFieldsSpecifiedUnfilteredList_success() { + Index indexLastPerson = Index.fromOneBased(model.getFilteredCustomerList().size()); + Customer lastCustomer = model.getFilteredCustomerList().get(indexLastPerson.getZeroBased()); + + CustomerBuilder personInList = new CustomerBuilder(lastCustomer); + Customer editedCustomer = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withTags(VALID_TAG_HUSBAND).build(); + + EditCustomerDescriptor descriptor = new EditCustomerDescriptorBuilder().withName(VALID_NAME_BOB) + .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build(); + EditCustomerCommand editCustomerCommand = new EditCustomerCommand(indexLastPerson, descriptor); + + String expectedMessage = String.format(EditCustomerCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedCustomer); + + Model expectedModel = new ModelManager(new InventorySystem(model.getInventorySystem()), new UserPrefs()); + expectedModel.setPerson(lastCustomer, editedCustomer); + + assertCommandSuccess(editCustomerCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_noFieldSpecifiedUnfilteredList_success() { + EditCustomerCommand editCustomerCommand = new EditCustomerCommand(INDEX_FIRST_PERSON, + new EditCustomerDescriptor()); + Customer editedCustomer = model.getFilteredCustomerList().get(INDEX_FIRST_PERSON.getZeroBased()); + + String expectedMessage = String.format(EditCustomerCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedCustomer); + + Model expectedModel = new ModelManager(new InventorySystem(model.getInventorySystem()), new UserPrefs()); + + assertCommandSuccess(editCustomerCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_filteredList_success() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Customer customerInFilteredList = model.getFilteredCustomerList().get(INDEX_FIRST_PERSON.getZeroBased()); + Customer editedCustomer = new CustomerBuilder(customerInFilteredList).withName(VALID_NAME_BOB).build(); + EditCustomerCommand editCustomerCommand = new EditCustomerCommand(INDEX_FIRST_PERSON, + new EditCustomerDescriptorBuilder().withName(VALID_NAME_BOB).build()); + + String expectedMessage = String.format(EditCustomerCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedCustomer); + + Model expectedModel = new ModelManager(new InventorySystem(model.getInventorySystem()), new UserPrefs()); + expectedModel.setPerson(model.getFilteredCustomerList().get(0), editedCustomer); + + assertCommandSuccess(editCustomerCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_duplicatePersonUnfilteredList_failure() { + Customer firstCustomer = model.getFilteredCustomerList().get(INDEX_FIRST_PERSON.getZeroBased()); + EditCustomerDescriptor descriptor = new EditCustomerDescriptorBuilder(firstCustomer).build(); + EditCustomerCommand editCustomerCommand = new EditCustomerCommand(INDEX_SECOND_PERSON, descriptor); + + assertCommandFailure(editCustomerCommand, model, Messages.MESSAGE_DUPLICATE_PERSON); + } + + @Test + public void execute_duplicatePersonFilteredList_failure() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + // edit customer in filtered list into a duplicate in address book + Customer secondCustomerInList = + model.getInventorySystem().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); + Customer firstCustomerInList = + model.getInventorySystem().getPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + EditCustomerDescriptor editCustomerDescriptor = + new EditCustomerDescriptorBuilder(secondCustomerInList).withId(firstCustomerInList.getId()).build(); + EditCustomerCommand editCustomerCommand = + new EditCustomerCommand(INDEX_FIRST_PERSON, editCustomerDescriptor); + + assertCommandFailure(editCustomerCommand, model, Messages.MESSAGE_DUPLICATE_PERSON); + } + + @Test + public void execute_invalidPersonIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredCustomerList().size() + 1); + EditCustomerDescriptor descriptor = new EditCustomerDescriptorBuilder().withName(VALID_NAME_BOB).build(); + EditCustomerCommand editCustomerCommand = new EditCustomerCommand(outOfBoundIndex, descriptor); + + assertCommandFailure(editCustomerCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + /** + * Edit filtered list where index is larger than size of filtered list, + * but smaller than size of address book + */ + @Test + public void execute_invalidPersonIndexFilteredList_failure() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + Index outOfBoundIndex = INDEX_SECOND_PERSON; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getInventorySystem().getPersonList().size()); + + EditCustomerCommand editCustomerCommand = new EditCustomerCommand(outOfBoundIndex, + new EditCustomerDescriptorBuilder().withName(VALID_NAME_BOB).build()); + + assertCommandFailure(editCustomerCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void equals() { + final EditCustomerCommand standardCommand = new EditCustomerCommand(INDEX_FIRST_PERSON, DESC_AMY); + + // same values -> returns true + EditCustomerDescriptor copyDescriptor = new EditCustomerDescriptor(DESC_AMY); + EditCustomerCommand commandWithSameValues = new EditCustomerCommand(INDEX_FIRST_PERSON, copyDescriptor); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearCustomerCommand())); + + // different index -> returns false + assertFalse(standardCommand.equals(new EditCustomerCommand(INDEX_SECOND_PERSON, DESC_AMY))); + + // different descriptor -> returns false + assertFalse(standardCommand.equals(new EditCustomerCommand(INDEX_FIRST_PERSON, DESC_BOB))); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/customer/EditCustomerDescriptorTest.java similarity index 64% rename from src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java rename to src/test/java/seedu/address/logic/commands/customer/EditCustomerDescriptorTest.java index e0288792e72..5d4f725a3b3 100644 --- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java +++ b/src/test/java/seedu/address/logic/commands/customer/EditCustomerDescriptorTest.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.customer; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -12,15 +12,15 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.testutil.EditPersonDescriptorBuilder; +import seedu.address.logic.commands.customer.EditCustomerCommand.EditCustomerDescriptor; +import seedu.address.testutil.customer.EditCustomerDescriptorBuilder; -public class EditPersonDescriptorTest { +public class EditCustomerDescriptorTest { @Test public void equals() { // same values -> returns true - EditPersonDescriptor descriptorWithSameValues = new EditPersonDescriptor(DESC_AMY); + EditCustomerCommand.EditCustomerDescriptor descriptorWithSameValues = new EditCustomerDescriptor(DESC_AMY); assertTrue(DESC_AMY.equals(descriptorWithSameValues)); // same object -> returns true @@ -36,23 +36,23 @@ public void equals() { assertFalse(DESC_AMY.equals(DESC_BOB)); // different name -> returns false - EditPersonDescriptor editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withName(VALID_NAME_BOB).build(); + EditCustomerDescriptor editedAmy = new EditCustomerDescriptorBuilder(DESC_AMY).withName(VALID_NAME_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); // different phone -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withPhone(VALID_PHONE_BOB).build(); + editedAmy = new EditCustomerDescriptorBuilder(DESC_AMY).withPhone(VALID_PHONE_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); // different email -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withEmail(VALID_EMAIL_BOB).build(); + editedAmy = new EditCustomerDescriptorBuilder(DESC_AMY).withEmail(VALID_EMAIL_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); // different address -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build(); + editedAmy = new EditCustomerDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); // different tags -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_HUSBAND).build(); + editedAmy = new EditCustomerDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_HUSBAND).build(); assertFalse(DESC_AMY.equals(editedAmy)); } } diff --git a/src/test/java/seedu/address/logic/commands/customer/FindCustomerCommandTest.java b/src/test/java/seedu/address/logic/commands/customer/FindCustomerCommandTest.java new file mode 100644 index 00000000000..404c7465075 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/customer/FindCustomerCommandTest.java @@ -0,0 +1,374 @@ +package seedu.address.logic.commands.customer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE; +import static seedu.address.testutil.customer.TypicalCustomers.BENSON; +import static seedu.address.testutil.customer.TypicalCustomers.CARL; +import static seedu.address.testutil.customer.TypicalCustomers.DANIEL; +import static seedu.address.testutil.customer.TypicalCustomers.ELLE; +import static seedu.address.testutil.customer.TypicalCustomers.FIONA; +import static seedu.address.testutil.customer.TypicalCustomers.GEORGE; +import static seedu.address.testutil.customer.TypicalCustomers.getTypicalInventorySystem; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.customer.AddressContainsKeywordsPredicate; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.EmailContainsKeywordsPredicate; +import seedu.address.model.customer.JointCustomerPredicate; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.NameContainsKeywordsPredicate; +import seedu.address.model.customer.PhoneContainsKeywordsPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code FindCustomerCommand}. + */ +public class FindCustomerCommandTest { + private Model model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + + @Test + public void equals() { + NameContainsKeywordsPredicate firstPredicate = + new NameContainsKeywordsPredicate(Collections.singletonList("first")); + NameContainsKeywordsPredicate secondPredicate = + new NameContainsKeywordsPredicate(Collections.singletonList("second")); + + FindCustomerCommand findFirstCommand = new FindCustomerCommand(firstPredicate); + FindCustomerCommand findSecondCommand = new FindCustomerCommand(secondPredicate); + + // same object -> returns true + assertTrue(findFirstCommand.equals(findFirstCommand)); + + // same values -> returns true + FindCustomerCommand findFirstCommandCopy = new FindCustomerCommand(firstPredicate); + assertTrue(findFirstCommand.equals(findFirstCommandCopy)); + + // different types -> returns false + assertFalse(findFirstCommand.equals(1)); + + // null -> returns false + assertFalse(findFirstCommand.equals(null)); + + // different customer -> returns false + assertFalse(findFirstCommand.equals(findSecondCommand)); + } + + @Test + public void execute_zeroKeywords_noCustomerFound() { + String expectedMessage = Name.MESSAGE_CONSTRAINTS; + NameContainsKeywordsPredicate predicate = preparePredicate(" "); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertEquals(Collections.emptyList(), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneAddressKeyword_noCustomerFound() { + String expectedMessage = "No customers staying in the area serangoon found!"; + AddressContainsKeywordsPredicate predicate = prepareAddressPredicate("serangoon"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertEquals(Collections.emptyList(), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneAddressKeyword_multipleCustomersFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); + AddressContainsKeywordsPredicate predicate = prepareAddressPredicate("street"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(CARL, DANIEL, GEORGE), model.getFilteredCustomerList()); + } + + @Test + public void execute_multipleAddressKeywords_oneCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + AddressContainsKeywordsPredicate predicate = prepareAddressPredicate("jurong bugis changi"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_multipleAddressKeywords_multipleCustomersFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); + AddressContainsKeywordsPredicate predicate = prepareAddressPredicate("jurong clementi tokyo"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE, BENSON, FIONA), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneNameKeyword_oneCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + NameContainsKeywordsPredicate predicate = preparePredicate("george"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(GEORGE), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneNameKeyword_multipleCustomersFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + NameContainsKeywordsPredicate predicate = preparePredicate("Meier"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(BENSON, DANIEL), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_multipleNameKeywords_oneCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + NameContainsKeywordsPredicate predicate = preparePredicate("george alex peter"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(GEORGE), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_multipleNameKeywords_multipleCustomersFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); + NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(CARL, ELLE, FIONA), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneEmailKeyword_noCustomerFound() { + String expectedMessage = "No customers with email jack@example.com found!"; + EmailContainsKeywordsPredicate predicate = prepareEmailPredicate("jack@example.com"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertEquals(Collections.emptyList(), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneEmailKeyword_oneCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + EmailContainsKeywordsPredicate predicate = prepareEmailPredicate("heinz@example.com"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(CARL), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_onePhoneKeyword_noCustomerFound() { + String expectedMessage = "No customers with phone number 81232712 found!"; + PhoneContainsKeywordsPredicate predicate = preparePhonePredicate("81232712"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertEquals(Collections.emptyList(), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_onePhoneKeyword_oneCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + PhoneContainsKeywordsPredicate predicate = preparePhonePredicate("87652533"); + FindCustomerCommand command = new FindCustomerCommand(predicate); + expectedModel.updateFilteredCustomerList(predicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(DANIEL), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneNameAndAddressKeyword_noCustomerFound() { + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("paul")); + predicates.add(prepareAddressPredicate("bugis")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertEquals(Collections.emptyList(), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneNameAndAddressKeyword_oneCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("pauline")); + predicates.add(prepareAddressPredicate("jurong")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + FindCustomerCommand command = new FindCustomerCommand(jointCustomerPredicate); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_multipleNameAndAddressKeyword_noCustomerFound() { + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("paul peter")); + predicates.add(prepareAddressPredicate("bugis bishan changi")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertEquals(Collections.emptyList(), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_multipleNameAndAddressKeyword_oneCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("pauline peter pan")); + predicates.add(prepareAddressPredicate("jurong bugis bishan")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + FindCustomerCommand command = new FindCustomerCommand(jointCustomerPredicate); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_multipleNameAndAddressKeyword_multipleCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("pauline benson")); + predicates.add(prepareAddressPredicate("jurong clementi")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + FindCustomerCommand command = new FindCustomerCommand(jointCustomerPredicate); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE, BENSON), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneNameAndPhoneKeyword_noCustomerFound() { + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("paul")); + predicates.add(preparePhonePredicate("9999999")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertEquals(Collections.emptyList(), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneNameAndPhoneKeyword_oneCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("pauline")); + predicates.add(preparePhonePredicate("94351253")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + FindCustomerCommand command = new FindCustomerCommand(jointCustomerPredicate); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_multipleNameAndPhoneKeyword_noCustomerFound() { + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("paul peter jojo")); + predicates.add(preparePhonePredicate("999999 122313123 7872318237")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertEquals(Collections.emptyList(), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_multipleNameAndPhoneKeyword_multipleCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("pauline benson")); + predicates.add(preparePhonePredicate("94351253 98765432")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + FindCustomerCommand command = new FindCustomerCommand(jointCustomerPredicate); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE, BENSON), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneNameAndEmailKeyword_noCustomerFound() { + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("paul")); + predicates.add(prepareEmailPredicate("test@example.com")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertEquals(Collections.emptyList(), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_oneNameAndEmailKeyword_oneCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("pauline")); + predicates.add(prepareEmailPredicate("alice@example.com")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + FindCustomerCommand command = new FindCustomerCommand(jointCustomerPredicate); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_multipleNameAndEmailKeyword_noCustomerFound() { + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("paul peter jojo")); + predicates.add(prepareEmailPredicate("test@example.com hahah@wahaha.com")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertEquals(Collections.emptyList(), expectedModel.getFilteredCustomerList()); + } + + @Test + public void execute_multipleNameAndEmailKeyword_multipleCustomerFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + List> predicates = new ArrayList<>(); + predicates.add(preparePredicate("pauline benson")); + predicates.add(prepareEmailPredicate("alice@example.com johnd@example.com")); + JointCustomerPredicate jointCustomerPredicate = new JointCustomerPredicate(predicates); + FindCustomerCommand command = new FindCustomerCommand(jointCustomerPredicate); + expectedModel.updateFilteredCustomerList(jointCustomerPredicate); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + assertEquals(Arrays.asList(ALICE, BENSON), expectedModel.getFilteredCustomerList()); + } + + /** + * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. + */ + private NameContainsKeywordsPredicate preparePredicate(String userInput) { + return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } + + /** + * Parses {@code userInput} into a {@code AddressContainsKeywordsPredicate}. + */ + private AddressContainsKeywordsPredicate prepareAddressPredicate(String userInput) { + return new AddressContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } + + /** + * Parses {@code userInput} into a {@code EmailContainsKeywordsPredicate}. + */ + private EmailContainsKeywordsPredicate prepareEmailPredicate(String userInput) { + return new EmailContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } + + /** + * Parses {@code userInput} into a {@code PhoneContainsKeywordsPredicate}. + */ + private PhoneContainsKeywordsPredicate preparePhonePredicate(String userInput) { + return new PhoneContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } +} diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/customer/ListCustomerCommandTest.java similarity index 57% rename from src/test/java/seedu/address/logic/commands/ListCommandTest.java rename to src/test/java/seedu/address/logic/commands/customer/ListCustomerCommandTest.java index 435ff1f7275..4f80558ef8d 100644 --- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/customer/ListCustomerCommandTest.java @@ -1,9 +1,9 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.customer; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.customer.TypicalCustomers.getTypicalInventorySystem; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -13,27 +13,27 @@ import seedu.address.model.UserPrefs; /** - * Contains integration tests (interaction with the Model) and unit tests for ListCommand. + * Contains integration tests (interaction with the Model) and unit tests for ListCustomerCommand. */ -public class ListCommandTest { +public class ListCustomerCommandTest { private Model model; private Model expectedModel; @BeforeEach public void setUp() { - model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + expectedModel = new ModelManager(model.getInventorySystem(), new UserPrefs()); } @Test public void execute_listIsNotFiltered_showsSameList() { - assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel); + assertCommandSuccess(new ListCustomerCommand(), model, ListCustomerCommand.MESSAGE_SUCCESS, expectedModel); } @Test public void execute_listIsFiltered_showsEverything() { showPersonAtIndex(model, INDEX_FIRST_PERSON); - assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel); + assertCommandSuccess(new ListCustomerCommand(), model, ListCustomerCommand.MESSAGE_SUCCESS, expectedModel); } } diff --git a/src/test/java/seedu/address/logic/commands/product/AddProductCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/product/AddProductCommandIntegrationTest.java new file mode 100644 index 00000000000..0a8bb3e2308 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/product/AddProductCommandIntegrationTest.java @@ -0,0 +1,47 @@ +package seedu.address.logic.commands.product; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.product.TypicalProducts.getTypicalInventorySystem; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.product.Product; +import seedu.address.testutil.product.TypicalProducts; + +/** + * Contains integration tests (interaction with the Model) for {@code AddCustomerCommand}. + */ +public class AddProductCommandIntegrationTest { + + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + } + + @Test + public void execute_newProduct_success() { + Product validProduct = TypicalProducts.WATCH; + + Model expectedModel = new ModelManager(model.getInventorySystem(), new UserPrefs()); + expectedModel.addProduct(validProduct); + + assertCommandSuccess(new AddProductCommand(validProduct), model, + String.format(AddProductCommand.MESSAGE_SUCCESS, validProduct), expectedModel); + } + + @Test + public void execute_duplicateProduct_throwsCommandException() { + Product productInList = model.getInventorySystem().getProductList().get(0); + assertCommandFailure(new AddProductCommand(productInList), model, + Messages.MESSAGE_DUPLICATE_PRODUCT); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/product/AddProductCommandTest.java b/src/test/java/seedu/address/logic/commands/product/AddProductCommandTest.java new file mode 100644 index 00000000000..765097fc55f --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/product/AddProductCommandTest.java @@ -0,0 +1,289 @@ +package seedu.address.logic.commands.product; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.product.TypicalProducts.ABACUS_ID; +import static seedu.address.testutil.product.TypicalProducts.BOOK_ID; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.UUID; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.InventorySystem; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyInventorySystem; +import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; +import seedu.address.testutil.product.ProductBuilder; + +public class AddProductCommandTest { + + @Test + public void constructor_nullProduct_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new AddProductCommand(null)); + } + + @Test + public void execute_productAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingProductAdded modelStub = new ModelStubAcceptingProductAdded(); + Product validProduct = new ProductBuilder(ABACUS_ID).build(); + + CommandResult commandResult = new AddProductCommand(validProduct).execute(modelStub); + + assertEquals(String.format(AddProductCommand.MESSAGE_SUCCESS, validProduct), + commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(validProduct), modelStub.productsAdded); + } + + @Test + public void execute_duplicateProduct_throwsCommandException() { + Product validProduct = new ProductBuilder(ABACUS_ID).build(); + AddProductCommand addProductCommand = new AddProductCommand(validProduct); + ModelStub modelStub = new ModelStubWithProduct(validProduct); + + assertThrows(CommandException.class, + Messages.MESSAGE_DUPLICATE_PRODUCT, () -> addProductCommand.execute(modelStub)); + } + + @Test + public void equals() { + Product alice = new ProductBuilder(ABACUS_ID).withDescription("Abacus").build(); + Product bob = new ProductBuilder(BOOK_ID).withDescription("Book").build(); + AddProductCommand addAliceCommand = new AddProductCommand(alice); + AddProductCommand addBobCommand = new AddProductCommand(bob); + + // same object -> returns true + assertTrue(addAliceCommand.equals(addAliceCommand)); + + // same values -> returns true + AddProductCommand addAliceCommandCopy = new AddProductCommand(alice); + assertTrue(addAliceCommand.equals(addAliceCommandCopy)); + + // different types -> returns false + assertFalse(addAliceCommand.equals(1)); + + // null -> returns false + assertFalse(addAliceCommand.equals(null)); + + // different product -> returns false + assertFalse(addAliceCommand.equals(addBobCommand)); + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getInventorySystemFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setInventorySystemFilePath(Path inventorySystemFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addPerson(Customer customer) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addProduct(Product product) { + + } + + @Override + public Product findProductById(UUID id) { + return null; + } + + @Override + public void setInventorySystem(ReadOnlyInventorySystem inventorySystem, String commandWord) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyInventorySystem getInventorySystem() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasPerson(Customer customer) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasProduct(Product product) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deletePerson(Customer target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteProduct(Product target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTransaction(Transaction target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setPerson(Customer target, Customer editedProduct) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setProduct(Product target, Product editedProduct) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTransaction(Transaction target, Transaction editedTransaction) { + + } + + @Override + public ObservableList getFilteredCustomerList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTransaction(Transaction transaction) { + return false; + } + + @Override + public void addTransaction(Transaction transaction) { + + } + + @Override + public ObservableList filterTransaction(Predicate predicate) { + return null; + } + + @Override + public void updateFilteredCustomerList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredCustomerList() { + + } + + @Override + public void updateFilteredProductList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredProductList() { + + } + + @Override + public void updateFilteredTransactionList(Predicate predicate) { + + } + + @Override + public void updateFilteredTransactionList() { + + } + + @Override + public ObservableList getFilteredProductList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTransactionList() { + return null; + } + } + + /** + * A Model stub that contains a single product. + */ + private class ModelStubWithProduct extends ModelStub { + private final Product product; + + ModelStubWithProduct(Product product) { + requireNonNull(product); + this.product = product; + } + + @Override + public boolean hasProduct(Product product) { + requireNonNull(product); + return this.product.isSameProduct(product); + } + } + + /** + * A Model stub that always accept the product being added. + */ + private class ModelStubAcceptingProductAdded extends ModelStub { + final ArrayList productsAdded = new ArrayList<>(); + + @Override + public boolean hasProduct(Product product) { + requireNonNull(product); + return productsAdded.stream().anyMatch(product::isSameProduct); + } + + @Override + public void addProduct(Product product) { + requireNonNull(product); + productsAdded.add(product); + } + + @Override + public ReadOnlyInventorySystem getInventorySystem() { + return new InventorySystem(); + } + } + +} + diff --git a/src/test/java/seedu/address/logic/commands/product/ClearProductCommandTest.java b/src/test/java/seedu/address/logic/commands/product/ClearProductCommandTest.java new file mode 100644 index 00000000000..a7f67458805 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/product/ClearProductCommandTest.java @@ -0,0 +1,32 @@ +package seedu.address.logic.commands.product; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.product.TypicalProducts.getTypicalInventorySystem; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.InventorySystem; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +public class ClearProductCommandTest { + + @Test + public void execute_emptyInventorySystem_success() { + Model model = new ModelManager(); + Model expectedModel = new ModelManager(); + + assertCommandSuccess(new ClearProductCommand(), model, ClearProductCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_nonEmptyInventorySystem_success() { + Model model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + expectedModel.setInventorySystem(new InventorySystem(), ClearProductCommand.COMMAND_WORD); + + assertCommandSuccess(new ClearProductCommand(), model, ClearProductCommand.MESSAGE_SUCCESS, expectedModel); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/product/DeleteProductCommandTest.java b/src/test/java/seedu/address/logic/commands/product/DeleteProductCommandTest.java new file mode 100644 index 00000000000..a4d108e4721 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/product/DeleteProductCommandTest.java @@ -0,0 +1,109 @@ +package seedu.address.logic.commands.product; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showProductAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.product.TypicalProducts.getTypicalInventorySystem; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.product.Product; + +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * {@code DeleteProductCommand}. + */ +public class DeleteProductCommandTest { + + private Model model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Product productToDelete = model.getFilteredProductList().get(INDEX_FIRST_PERSON.getZeroBased()); + DeleteProductCommand deleteProductCommand = new DeleteProductCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(DeleteProductCommand.MESSAGE_DELETE_PRODUCT_SUCCESS, productToDelete); + + ModelManager expectedModel = new ModelManager(model.getInventorySystem(), new UserPrefs()); + expectedModel.deleteProduct(productToDelete); + + assertCommandSuccess(deleteProductCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredProductList().size() + 1); + DeleteProductCommand deleteProductCommand = new DeleteProductCommand(outOfBoundIndex); + + assertCommandFailure(deleteProductCommand, model, Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexFilteredList_success() { + showProductAtIndex(model, INDEX_FIRST_PERSON); + + Product productToDelete = model.getFilteredProductList().get(INDEX_FIRST_PERSON.getZeroBased()); + DeleteProductCommand deleteProductCommand = new DeleteProductCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(DeleteProductCommand.MESSAGE_DELETE_PRODUCT_SUCCESS, productToDelete); + + Model expectedModel = new ModelManager(model.getInventorySystem(), new UserPrefs()); + expectedModel.deleteProduct(productToDelete); + showNoProduct(expectedModel); + + assertCommandSuccess(deleteProductCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showProductAtIndex(model, INDEX_FIRST_PERSON); + + Index outOfBoundIndex = INDEX_SECOND_PERSON; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getInventorySystem().getProductList().size()); + + DeleteProductCommand deleteProductCommand = new DeleteProductCommand(outOfBoundIndex); + + assertCommandFailure(deleteProductCommand, model, Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX); + } + + @Test + public void equals() { + DeleteProductCommand deleteFirstCommand = new DeleteProductCommand(INDEX_FIRST_PERSON); + DeleteProductCommand deleteSecondCommand = new DeleteProductCommand(INDEX_SECOND_PERSON); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + DeleteProductCommand deleteFirstCommandCopy = new DeleteProductCommand(INDEX_FIRST_PERSON); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different product -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } + + /** + * Updates {@code model}'s filtered list to show no one. + */ + private void showNoProduct(Model model) { + model.updateFilteredProductList(p -> false); + + assertTrue(model.getFilteredProductList().isEmpty()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/product/EditProductCommandTest.java b/src/test/java/seedu/address/logic/commands/product/EditProductCommandTest.java new file mode 100644 index 00000000000..874ff3013ce --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/product/EditProductCommandTest.java @@ -0,0 +1,183 @@ +package seedu.address.logic.commands.product; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.DESC_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PRICE_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showProductAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PRODUCT; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PRODUCT; +import static seedu.address.testutil.product.TypicalProducts.ABACUS_ID; +import static seedu.address.testutil.product.TypicalProducts.getTypicalInventorySystem; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.product.EditProductCommand.EditProductDescriptor; +import seedu.address.model.InventorySystem; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.product.Product; +import seedu.address.testutil.product.EditProductDescriptorBuilder; +import seedu.address.testutil.product.ProductBuilder; + +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests + * for EditProductCommand. + */ +public class EditProductCommandTest { + + private Model model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + + @Test + public void execute_allFieldsSpecifiedUnfilteredList_success() { + Product editedProduct = new ProductBuilder(ABACUS_ID).build(); + EditProductDescriptor descriptor = new EditProductDescriptorBuilder(editedProduct).build(); + + EditProductCommand editProductCommand = new EditProductCommand(INDEX_FIRST_PRODUCT, descriptor); + + String expectedMessage = String.format(EditProductCommand.MESSAGE_EDIT_PRODUCT_SUCCESS, editedProduct); + + Model expectedModel = new ModelManager(new InventorySystem(model.getInventorySystem()), new UserPrefs()); + expectedModel.setProduct(model.getFilteredProductList().get(0), editedProduct); + + assertCommandSuccess(editProductCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_someFieldsSpecifiedUnfilteredList_success() { + Index indexLastProduct = Index.fromOneBased(model.getFilteredProductList().size()); + Product lastProduct = model.getFilteredProductList().get(indexLastProduct.getZeroBased()); + + ProductBuilder productInList = new ProductBuilder(lastProduct); + Product editedProduct = productInList.withDescription(VALID_DESCRIPTION_WATCH) + .withPrice(VALID_PRICE_WATCH).build(); + + EditProductDescriptor descriptor = new EditProductDescriptorBuilder(editedProduct) + .withDescription(VALID_DESCRIPTION_WATCH) + .withPrice(VALID_PRICE_WATCH).build(); + EditProductCommand editProductCommand = new EditProductCommand(indexLastProduct, descriptor); + + String expectedMessage = String.format(EditProductCommand.MESSAGE_EDIT_PRODUCT_SUCCESS, editedProduct); + + Model expectedModel = new ModelManager(new InventorySystem(model.getInventorySystem()), new UserPrefs()); + expectedModel.setProduct(lastProduct, editedProduct); + + assertCommandSuccess(editProductCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_noFieldSpecifiedUnfilteredList_success() { + Product editedProduct = model.getFilteredProductList().get(INDEX_FIRST_PRODUCT.getZeroBased()); + + EditProductCommand editProductCommand = new EditProductCommand(INDEX_FIRST_PRODUCT, + new EditProductDescriptorBuilder(editedProduct).build()); + String expectedMessage = String.format(EditProductCommand.MESSAGE_EDIT_PRODUCT_SUCCESS, editedProduct); + + Model expectedModel = new ModelManager(new InventorySystem(model.getInventorySystem()), new UserPrefs()); + + assertCommandSuccess(editProductCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_filteredList_success() { + showProductAtIndex(model, INDEX_FIRST_PRODUCT); + + Product productInFilteredList = model.getFilteredProductList().get(INDEX_FIRST_PRODUCT.getZeroBased()); + Product editedProduct = new + ProductBuilder(productInFilteredList).withDescription(VALID_DESCRIPTION_WATCH).build(); + + EditProductCommand editProductCommand = new EditProductCommand(INDEX_FIRST_PRODUCT, + new EditProductDescriptorBuilder(productInFilteredList) + .withDescription(VALID_DESCRIPTION_WATCH).build()); + + String expectedMessage = String.format(EditProductCommand.MESSAGE_EDIT_PRODUCT_SUCCESS, editedProduct); + + Model expectedModel = new ModelManager(new InventorySystem(model.getInventorySystem()), new UserPrefs()); + expectedModel.setProduct(model.getFilteredProductList().get(0), editedProduct); + + // assertCommandSuccess(editProductCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_duplicateProductUnfilteredList_failure() { + Product firstProduct = model.getFilteredProductList().get(INDEX_FIRST_PRODUCT.getZeroBased()); + EditProductDescriptor descriptor = new EditProductDescriptorBuilder(firstProduct).build(); + EditProductCommand editProductCommand = new EditProductCommand(INDEX_SECOND_PRODUCT, descriptor); + + assertCommandFailure(editProductCommand, model, Messages.MESSAGE_DUPLICATE_PRODUCT); + } + + @Test + public void execute_duplicateProductFilteredList_failure() { + showProductAtIndex(model, INDEX_FIRST_PRODUCT); + + // edit product in filtered list into a duplicate in address book + Product productInList = model.getInventorySystem().getProductList().get(INDEX_SECOND_PRODUCT.getZeroBased()); + EditProductCommand editProductCommand = new EditProductCommand(INDEX_FIRST_PRODUCT, + new EditProductDescriptorBuilder(productInList).build()); + + assertCommandFailure(editProductCommand, model, Messages.MESSAGE_DUPLICATE_PRODUCT); + } + + @Test + public void execute_invalidProductIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredProductList().size() + 1); + Product editedProduct = new ProductBuilder(ABACUS_ID).build(); + EditProductDescriptor descriptor = new EditProductDescriptorBuilder(editedProduct).build(); + EditProductCommand editProductCommand = new EditProductCommand(outOfBoundIndex, descriptor); + + assertCommandFailure(editProductCommand, model, Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX); + } + + /** + * Edit filtered list where index is larger than size of filtered list, + * but smaller than size of address book + */ + @Test + public void execute_invalidProductIndexFilteredList_failure() { + showProductAtIndex(model, INDEX_FIRST_PRODUCT); + Index outOfBoundIndex = INDEX_SECOND_PRODUCT; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getInventorySystem().getProductList().size()); + + Product editedProduct = new ProductBuilder(ABACUS_ID).build(); + EditProductDescriptor descriptor = new EditProductDescriptorBuilder(editedProduct).build(); + + EditProductCommand editProductCommand = new EditProductCommand(outOfBoundIndex, descriptor); + + assertCommandFailure(editProductCommand, model, Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX); + } + + @Test + public void equals() { + final EditProductCommand standardCommand = new EditProductCommand(INDEX_FIRST_PRODUCT, DESC_BAG); + + // same values -> returns true + EditProductDescriptor copyDescriptor = new EditProductDescriptor(DESC_BAG); + EditProductCommand commandWithSameValues = new EditProductCommand(INDEX_FIRST_PRODUCT, copyDescriptor); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearProductCommand())); + + // different index -> returns false + assertFalse(standardCommand.equals(new EditProductCommand(INDEX_SECOND_PRODUCT, DESC_BAG))); + + // different descriptor -> returns false + assertFalse(standardCommand.equals(new EditProductCommand(INDEX_FIRST_PRODUCT, DESC_WATCH))); + } +} + diff --git a/src/test/java/seedu/address/logic/commands/product/EditProductDescriptorTest.java b/src/test/java/seedu/address/logic/commands/product/EditProductDescriptorTest.java new file mode 100644 index 00000000000..370a33d6ce5 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/product/EditProductDescriptorTest.java @@ -0,0 +1,54 @@ +package seedu.address.logic.commands.product; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.DESC_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PRICE_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_QUANTITY_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SALES_WATCH; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.product.EditProductCommand.EditProductDescriptor; +import seedu.address.testutil.product.EditProductDescriptorBuilder; + +public class EditProductDescriptorTest { + + @Test + public void equals() { + // same values -> returns true + EditProductDescriptor descriptorWithSameValues = new EditProductDescriptor(DESC_BAG); + assertTrue(DESC_BAG.equals(descriptorWithSameValues)); + + // same object -> returns true + assertTrue(DESC_BAG.equals(DESC_BAG)); + + // null -> returns false + assertFalse(DESC_BAG.equals(null)); + + // different types -> returns false + assertFalse(DESC_BAG.equals(5)); + + // different values -> returns false + assertFalse(DESC_BAG.equals(DESC_WATCH)); + + // different description -> returns false + EditProductDescriptor editedAmy = new + EditProductDescriptorBuilder(DESC_BAG).withDescription(VALID_DESCRIPTION_WATCH).build(); + assertFalse(DESC_BAG.equals(editedAmy)); + + // different price -> returns false + editedAmy = new EditProductDescriptorBuilder(DESC_BAG).withPrice(VALID_PRICE_WATCH).build(); + assertFalse(DESC_BAG.equals(editedAmy)); + + // different quantity -> returns false + editedAmy = new EditProductDescriptorBuilder(DESC_BAG).withQuantity(VALID_QUANTITY_WATCH).build(); + assertFalse(DESC_BAG.equals(editedAmy)); + + // different sales -> returns false + editedAmy = new EditProductDescriptorBuilder(DESC_BAG).withSales(VALID_SALES_WATCH).build(); + assertFalse(DESC_BAG.equals(editedAmy)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/product/FindProductCommandTest.java b/src/test/java/seedu/address/logic/commands/product/FindProductCommandTest.java new file mode 100644 index 00000000000..7b4edc127a2 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/product/FindProductCommandTest.java @@ -0,0 +1,78 @@ +package seedu.address.logic.commands.product; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_PRODUCTS_LISTED_OVERVIEW; +import static seedu.address.testutil.product.TypicalProducts.getTypicalInventorySystem; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.product.DescriptionContainsKeywordsPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code FindProductCommand}. + */ +public class FindProductCommandTest { + private Model model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + + @Test + public void equals() { + DescriptionContainsKeywordsPredicate firstPredicate = + new DescriptionContainsKeywordsPredicate(Collections.singletonList("first")); + DescriptionContainsKeywordsPredicate secondPredicate = + new DescriptionContainsKeywordsPredicate(Collections.singletonList("second")); + + FindProductCommand findFirstCommand = new FindProductCommand(firstPredicate); + FindProductCommand findSecondCommand = new FindProductCommand(secondPredicate); + + // same object -> returns true + assertTrue(findFirstCommand.equals(findFirstCommand)); + + // same values -> returns true + FindProductCommand findFirstCommandCopy = new FindProductCommand(firstPredicate); + assertTrue(findFirstCommand.equals(findFirstCommandCopy)); + + // different types -> returns false + assertFalse(findFirstCommand.equals(1)); + + // null -> returns false + assertFalse(findFirstCommand.equals(null)); + + // different product -> returns false + assertFalse(findFirstCommand.equals(findSecondCommand)); + } + + @Test + public void execute_zeroKeywords_noProductFound() { + String expectedMessage = "Please enter at least one keyword!"; + DescriptionContainsKeywordsPredicate predicate = preparePredicate(" "); + FindProductCommand command = new FindProductCommand(predicate); + expectedModel.updateFilteredProductList(predicate); + // assertCommandSuccess(command, model, expectedMessage, expectedModel); + // assertEquals(Collections.emptyList(), model.getFilteredProductList()); + } + + @Test + public void execute_multipleKeywords_multipleProductsFound() { + String expectedMessage = String.format(MESSAGE_PRODUCTS_LISTED_OVERVIEW, 3); + DescriptionContainsKeywordsPredicate predicate = preparePredicate("bag book"); + FindProductCommand command = new FindProductCommand(predicate); + expectedModel.updateFilteredProductList(predicate); + // assertCommandSuccess(command, model, expectedMessage, expectedModel); + // assertEquals(Arrays.asList(BAG, BOOK), model.getFilteredProductList()); + } + + /** + * Parses {@code userInput} into a {@code DescriptionContainsKeywordsPredicate}. + */ + private DescriptionContainsKeywordsPredicate preparePredicate(String userInput) { + return new DescriptionContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } +} diff --git a/src/test/java/seedu/address/logic/commands/product/ListProductCommandTest.java b/src/test/java/seedu/address/logic/commands/product/ListProductCommandTest.java new file mode 100644 index 00000000000..f4f11ca4d3e --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/product/ListProductCommandTest.java @@ -0,0 +1,39 @@ +package seedu.address.logic.commands.product; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showProductAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PRODUCT; +import static seedu.address.testutil.product.TypicalProducts.getTypicalInventorySystem; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +/** + * Contains integration tests (interaction with the Model) and unit tests for ListProductCommand. + */ +public class ListProductCommandTest { + + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + expectedModel = new ModelManager(model.getInventorySystem(), new UserPrefs()); + } + + @Test + public void execute_listIsNotFiltered_showsSameList() { + assertCommandSuccess(new ListProductCommand(), model, ListProductCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_listIsFiltered_showsEverything() { + showProductAtIndex(model, INDEX_FIRST_PRODUCT); + assertCommandSuccess(new ListProductCommand(), model, ListProductCommand.MESSAGE_SUCCESS, expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/commands/product/LowLimitCommandTest.java b/src/test/java/seedu/address/logic/commands/product/LowLimitCommandTest.java new file mode 100644 index 00000000000..1cc4f61023a --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/product/LowLimitCommandTest.java @@ -0,0 +1,58 @@ +package seedu.address.logic.commands.product; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.THRESHOLD_BAG; +import static seedu.address.logic.commands.CommandTestUtil.THRESHOLD_WATCH; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PRODUCT; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PRODUCT; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.model.util.QuantityThreshold; + +public class LowLimitCommandTest { + @Test + public void constructor_nullLowLimit_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new LowLimitCommand(null, null)); + } + + @Test + public void constructor_negativeLowLimit_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new LowLimitCommand( + new Index(0), new QuantityThreshold("-1"))); + } + + @Test + public void constructor_negativeIndex_throwsIndexOutOfBoundsException() { + assertThrows(IndexOutOfBoundsException.class, () -> new LowLimitCommand( + new Index(-1), new QuantityThreshold("10"))); + } + + @Test + public void equals() { + final LowLimitCommand standardCommand = new LowLimitCommand(INDEX_FIRST_PRODUCT, THRESHOLD_BAG); + + // same values -> returns true + QuantityThreshold copyThreshold = new QuantityThreshold(THRESHOLD_BAG.value); + LowLimitCommand commandWithSameValues = new LowLimitCommand(INDEX_FIRST_PRODUCT, copyThreshold); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearProductCommand())); + + // different index -> returns false + assertFalse(standardCommand.equals(new LowLimitCommand(INDEX_SECOND_PRODUCT, THRESHOLD_BAG))); + + // different descriptor -> returns false + assertFalse(standardCommand.equals(new LowLimitCommand(INDEX_FIRST_PRODUCT, THRESHOLD_WATCH))); + } +} diff --git a/src/test/java/seedu/address/logic/commands/statistics/ProfitCommandTest.java b/src/test/java/seedu/address/logic/commands/statistics/ProfitCommandTest.java new file mode 100644 index 00000000000..52655361e37 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/statistics/ProfitCommandTest.java @@ -0,0 +1,45 @@ +package seedu.address.logic.commands.statistics; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.transaction.DateTime; +import seedu.address.testutil.transaction.DateTimeBuilder; + +public class ProfitCommandTest { + @Test + public void constructor_nullProfit_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new ProfitCommand(null, null)); + } + + @Test + public void equals() { + DateTime startDate = new DateTimeBuilder().build(); + DateTime endDate = new DateTimeBuilder(DateTimeBuilder.DEFAULT_END_DATE).build(); + + DateTime otherStartDate = new DateTimeBuilder(DateTimeBuilder.DEFAULT_OTHER_START_DATE).build(); + DateTime otherEndDate = new DateTimeBuilder(DateTimeBuilder.DEFAULT_OTHER_END_DATE).build(); + + ProfitCommand profitCommand = new ProfitCommand(startDate, endDate); + ProfitCommand otherProfitCommand = new ProfitCommand(otherStartDate, otherEndDate); + + // same object -> returns true + assertTrue(profitCommand.equals(profitCommand)); + + // same values -> returns true + ProfitCommand profitCommandCopy = new ProfitCommand(startDate, endDate); + assertTrue(profitCommand.equals(profitCommandCopy)); + + // different types -> returns false + assertFalse(profitCommand.equals(1)); + + // null -> returns false + assertFalse(profitCommand.equals(null)); + + // different product -> returns false + assertFalse(profitCommand.equals(otherProfitCommand)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/statistics/RevenueCommandTest.java b/src/test/java/seedu/address/logic/commands/statistics/RevenueCommandTest.java new file mode 100644 index 00000000000..3419b7ac643 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/statistics/RevenueCommandTest.java @@ -0,0 +1,45 @@ +package seedu.address.logic.commands.statistics; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.transaction.DateTime; +import seedu.address.testutil.transaction.DateTimeBuilder; + +public class RevenueCommandTest { + @Test + public void constructor_nullRevenue_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new RevenueCommand(null, null)); + } + + @Test + public void equals() { + DateTime startDate = new DateTimeBuilder().build(); + DateTime endDate = new DateTimeBuilder(DateTimeBuilder.DEFAULT_END_DATE).build(); + + DateTime otherStartDate = new DateTimeBuilder(DateTimeBuilder.DEFAULT_OTHER_START_DATE).build(); + DateTime otherEndDate = new DateTimeBuilder(DateTimeBuilder.DEFAULT_OTHER_END_DATE).build(); + + RevenueCommand revenueCommand = new RevenueCommand(startDate, endDate); + RevenueCommand otherRevenueCommand = new RevenueCommand(otherStartDate, otherEndDate); + + // same object -> returns true + assertTrue(revenueCommand.equals(revenueCommand)); + + // same values -> returns true + RevenueCommand revenueCommandCopy = new RevenueCommand(startDate, endDate); + assertTrue(revenueCommand.equals(revenueCommandCopy)); + + // different types -> returns false + assertFalse(revenueCommand.equals(1)); + + // null -> returns false + assertFalse(revenueCommand.equals(null)); + + // different product -> returns false + assertFalse(revenueCommand.equals(otherRevenueCommand)); + } +} diff --git a/src/test/java/seedu/address/logic/commands/transaction/AddTransactionCommandTest.java b/src/test/java/seedu/address/logic/commands/transaction/AddTransactionCommandTest.java new file mode 100644 index 00000000000..b445d162fee --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/transaction/AddTransactionCommandTest.java @@ -0,0 +1,359 @@ +package seedu.address.logic.commands.transaction; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE; +import static seedu.address.testutil.product.TypicalProducts.BAG; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.UUID; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; + +import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.InventorySystem; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyInventorySystem; +import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.transaction.TransactionFactory; +import seedu.address.testutil.product.ProductBuilder; +import seedu.address.testutil.transaction.TransactionFactoryBuilder; + +public class AddTransactionCommandTest { + + @Test + public void constructor_nullTransaction_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new AddTransactionCommand(null)); + } + + @Test + public void execute_transactionAcceptedByModel_addSuccessful() throws Exception { + AddTransactionCommandTest.ModelStubAcceptingTransactionAdded modelStub = + new AddTransactionCommandTest.ModelStubAcceptingTransactionAdded(); + TransactionFactory validTransactionFactory = new TransactionFactoryBuilder().build(); + Transaction validTransaction = validTransactionFactory.createTransaction(modelStub); + Product validProductToEdit = BAG; + Product validEditedProduct = new ProductBuilder(BAG).withQuantity( + BAG.getQuantity().minus(validTransaction.getQuantity()).toString() + ).build(); + + CommandResult commandResult = new AddTransactionCommand(validTransactionFactory).execute(modelStub); + + assertEquals(String.format(AddTransactionCommand.MESSAGE_SUCCESS, validTransaction), + commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(validTransaction), modelStub.transactionsAdded); + assertEquals(validProductToEdit, modelStub.productToEdit); + assertEquals(validEditedProduct, modelStub.editedProduct); + } + + @Test + public void execute_duplicateTransaction_throwsCommandException() throws Exception { + AddTransactionCommandTest.ModelStubWithTransaction modelStub = + new AddTransactionCommandTest.ModelStubWithTransaction(); + + TransactionFactory validTransactionFactory = new TransactionFactoryBuilder().build(); + Transaction validTransaction = validTransactionFactory.createTransaction(modelStub); + + AddTransactionCommand addTransactionCommand = new AddTransactionCommand(validTransactionFactory); + modelStub.setTransaction(validTransaction); + + assertThrows(CommandException.class, + Messages.MESSAGE_DUPLICATE_TRANSACTION, () -> addTransactionCommand.execute(modelStub)); + } + + @Test + public void equals() { + TransactionFactory tOne = new TransactionFactoryBuilder().withDescription("under discount").build(); + TransactionFactory tTwo = new TransactionFactoryBuilder().withDescription("NA").build(); + AddTransactionCommand addTOneCommand = new AddTransactionCommand(tOne); + AddTransactionCommand addTTwoCommand = new AddTransactionCommand(tTwo); + + // same object -> returns true + assertTrue(addTOneCommand.equals(addTOneCommand)); + + // same values -> returns true + AddTransactionCommand addTOneCommandCopy = new AddTransactionCommand(tOne); + assertTrue(addTOneCommand.equals(addTOneCommandCopy)); + + // different types -> returns false + assertFalse(addTOneCommand.equals(1)); + + // null -> returns false + assertFalse(addTOneCommand.equals(null)); + + // different transaction -> returns false + assertFalse(addTOneCommand.equals(addTTwoCommand)); + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getInventorySystemFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setInventorySystemFilePath(Path inventorySystemFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addPerson(Customer customer) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addProduct(Product product) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Product findProductById(UUID id) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setInventorySystem(ReadOnlyInventorySystem inventorySystem, String commandWord) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyInventorySystem getInventorySystem() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasPerson(Customer customer) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasProduct(Product product) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deletePerson(Customer target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteProduct(Product product) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTransaction(Transaction target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setPerson(Customer target, Customer editedTransaction) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setProduct(Product target, Product editedProduct) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTransaction(Transaction target, Transaction editedTransaction) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredCustomerList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTransaction(Transaction transaction) { + return false; + } + + @Override + public void addTransaction(Transaction transaction) { + + } + + @Override + public ObservableList filterTransaction(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredCustomerList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredCustomerList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredProductList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredProductList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTransactionList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTransactionList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredProductList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTransactionList() { + throw new AssertionError("This method should not be called."); + } + } + + /** + * A Model stub that contains a single transaction. + */ + private class ModelStubWithTransaction extends AddTransactionCommandTest.ModelStub { + private Transaction transaction; + private final FilteredList filteredCustomerList = + new FilteredList<>(FXCollections.observableArrayList(ALICE)); + private final FilteredList filteredProductList = + new FilteredList<>(FXCollections.observableArrayList(BAG)); + + + public void setTransaction(Transaction transaction) { + requireNonNull(transaction); + this.transaction = transaction; + } + + @Override + public boolean hasTransaction(Transaction transaction) { + requireNonNull(transaction); + return this.transaction.isSameTransaction(transaction); + } + + @Override + public ObservableList getFilteredCustomerList() { + return filteredCustomerList; + } + + @Override + public ObservableList getFilteredProductList() { + return filteredProductList; + } + + } + + /** + * A Model stub that always accept the transaction being added. + */ + private class ModelStubAcceptingTransactionAdded extends AddTransactionCommandTest.ModelStub { + final ArrayList transactionsAdded = new ArrayList<>(); + final FilteredList filteredCustomerList = + new FilteredList<>(FXCollections.observableArrayList(ALICE)); + final FilteredList filteredProductList = + new FilteredList<>(FXCollections.observableArrayList(BAG)); + + private Product productToEdit; + private Product editedProduct; + + @Override + public ObservableList getFilteredCustomerList() { + return filteredCustomerList; + } + + @Override + public ObservableList getFilteredProductList() { + return filteredProductList; + } + + @Override + public boolean hasTransaction(Transaction transaction) { + requireNonNull(transaction); + return transactionsAdded.stream().anyMatch(transaction::isSameTransaction); + } + + @Override + public void addTransaction(Transaction transaction) { + requireNonNull(transaction); + transactionsAdded.add(transaction); + } + + @Override + public void setProduct(Product target, Product editedProduct) { + productToEdit = target; + this.editedProduct = editedProduct; + } + + @Override + public void updateFilteredProductList() { + + } + + public Product getProductToEdit() { + return productToEdit; + } + + public Product getEditedProduct() { + return editedProduct; + } + + @Override + public ReadOnlyInventorySystem getInventorySystem() { + return new InventorySystem(); + } + } + +} + diff --git a/src/test/java/seedu/address/logic/commands/transaction/ClearTransactionCommandTest.java b/src/test/java/seedu/address/logic/commands/transaction/ClearTransactionCommandTest.java new file mode 100644 index 00000000000..4f49dfc5486 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/transaction/ClearTransactionCommandTest.java @@ -0,0 +1,33 @@ +package seedu.address.logic.commands.transaction; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.customer.TypicalCustomers.getTypicalInventorySystem; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.InventorySystem; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +public class ClearTransactionCommandTest { + + @Test + public void execute_emptyInventorySystem_success() { + Model model = new ModelManager(); + Model expectedModel = new ModelManager(); + + assertCommandSuccess(new ClearTransactionCommand(), model, + ClearTransactionCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_nonEmptyInventorySystem_success() { + Model model = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalInventorySystem(), new UserPrefs()); + expectedModel.setInventorySystem(new InventorySystem(), ClearTransactionCommand.COMMAND_WORD); + + assertCommandSuccess(new ClearTransactionCommand(), model, + ClearTransactionCommand.MESSAGE_SUCCESS, expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/commands/transaction/FindTransactionCommandTest.java b/src/test/java/seedu/address/logic/commands/transaction/FindTransactionCommandTest.java new file mode 100644 index 00000000000..1cbf20c8024 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/transaction/FindTransactionCommandTest.java @@ -0,0 +1,322 @@ +package seedu.address.logic.commands.transaction; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.transaction.TypicalTransactions.ALICE_BUY_ONE_BAG_MARCH_FIRST; +import static seedu.address.testutil.transaction.TypicalTransactions.ALICE_BUY_ONE_BAG_MARCH_SECOND; +import static seedu.address.testutil.transaction.TypicalTransactions.ALICE_BUY_TWO_BAG_MARCH_FIRST; +import static seedu.address.testutil.transaction.TypicalTransactions.BENSON_BUY_ONE_BAG_MARCH_FIRST; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; + +import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyInventorySystem; +import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.CustomerContainsKeywordPredicate; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.DateTimeEqualsPredicate; +import seedu.address.model.transaction.Transaction; +import seedu.address.testutil.transaction.DateTimeBuilder; + +public class FindTransactionCommandTest { + + @Test + public void equals() { + CustomerContainsKeywordPredicate predicateAlice = + new CustomerContainsKeywordPredicate(Collections.singletonList("Alice")); + CustomerContainsKeywordPredicate predicateBob = + new CustomerContainsKeywordPredicate(Collections.singletonList("Bob")); + + FindTransactionCommand findNamesHasAlice = new FindTransactionCommand(predicateAlice); + FindTransactionCommand findNameHasBob = new FindTransactionCommand(predicateBob); + + // same object -> returns true + assertTrue(findNamesHasAlice.equals(findNamesHasAlice)); + + // same values -> returns true + FindTransactionCommand findNamesHasAliceCopy = new FindTransactionCommand(predicateAlice); + assertTrue(findNamesHasAlice.equals(findNamesHasAliceCopy)); + + // different types -> return false + assertFalse(findNamesHasAlice.equals(1)); + + // null -> returns false + assertFalse(findNamesHasAlice.equals(null)); + + // different product -> returns false + assertFalse(findNamesHasAlice.equals(findNameHasBob)); + } + + @Test + public void execute_zeroKeywords_noTransactionFound() { + CustomerContainsKeywordPredicate predicate = + new CustomerContainsKeywordPredicate(Collections.emptyList()); + FindTransactionCommandTest.ModelStubWithTransaction modelStub = + new FindTransactionCommandTest.ModelStubWithTransaction(); + + FindTransactionCommand command = new FindTransactionCommand(predicate); + + CommandResult commandResult = command.execute(modelStub); + + assertEquals(String.format(Messages.MESSAGE_TRANSACTIONS_LISTED_OVERVIEW, 0), + commandResult.getFeedbackToUser()); + assertEquals(Collections.emptyList(), modelStub.getFilteredTransactionList()); + } + + @Test + public void execute_oneCustomerNameKeyword_oneTransactionFound() { + CustomerContainsKeywordPredicate predicate = + new CustomerContainsKeywordPredicate(Arrays.asList("Benson")); + FindTransactionCommandTest.ModelStubWithTransaction modelStub = + new FindTransactionCommandTest.ModelStubWithTransaction(); + + FindTransactionCommand command = new FindTransactionCommand(predicate); + + CommandResult commandResult = command.execute(modelStub); + + assertEquals(String.format(Messages.MESSAGE_TRANSACTIONS_LISTED_OVERVIEW, 1), + commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(BENSON_BUY_ONE_BAG_MARCH_FIRST), modelStub.getFilteredTransactionList()); + } + + @Test + public void execute_oneCustomerNameKeyword_threeTransactionFound() { + CustomerContainsKeywordPredicate predicate = + new CustomerContainsKeywordPredicate(Arrays.asList("Alice")); + FindTransactionCommandTest.ModelStubWithTransaction modelStub = + new FindTransactionCommandTest.ModelStubWithTransaction(); + + FindTransactionCommand command = new FindTransactionCommand(predicate); + + CommandResult commandResult = command.execute(modelStub); + + assertEquals(String.format(Messages.MESSAGE_TRANSACTIONS_LISTED_OVERVIEW, 3), + commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList( + ALICE_BUY_ONE_BAG_MARCH_FIRST, + ALICE_BUY_TWO_BAG_MARCH_FIRST, + ALICE_BUY_ONE_BAG_MARCH_SECOND + ), modelStub.getFilteredTransactionList()); + } + + @Test + public void execute_oneDateTime_oneTransactionFound() { + DateTime marchFirst = new DateTimeBuilder("2020-03-01 10:00").build(); + + DateTimeEqualsPredicate predicate = + new DateTimeEqualsPredicate(marchFirst); + FindTransactionCommandTest.ModelStubWithTransaction modelStub = + new FindTransactionCommandTest.ModelStubWithTransaction(); + + FindTransactionCommand command = new FindTransactionCommand(predicate); + + CommandResult commandResult = command.execute(modelStub); + + assertEquals(String.format(Messages.MESSAGE_TRANSACTIONS_LISTED_OVERVIEW, 3), + commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList( + ALICE_BUY_ONE_BAG_MARCH_FIRST, + ALICE_BUY_TWO_BAG_MARCH_FIRST, + BENSON_BUY_ONE_BAG_MARCH_FIRST + ), modelStub.getFilteredTransactionList()); + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getInventorySystemFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setInventorySystemFilePath(Path inventorySystemFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addPerson(Customer customer) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addProduct(Product product) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Product findProductById(UUID id) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setInventorySystem(ReadOnlyInventorySystem inventorySystem, String commandWord) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyInventorySystem getInventorySystem() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasPerson(Customer customer) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasProduct(Product product) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deletePerson(Customer target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteProduct(Product product) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTransaction(Transaction target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setPerson(Customer target, Customer editedTransaction) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setProduct(Product target, Product editedProduct) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTransaction(Transaction target, Transaction editedTransaction) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredCustomerList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTransaction(Transaction transaction) { + return false; + } + + @Override + public void addTransaction(Transaction transaction) { + + } + + @Override + public ObservableList filterTransaction(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredCustomerList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredCustomerList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredProductList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredProductList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTransactionList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTransactionList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredProductList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTransactionList() { + throw new AssertionError("This method should not be called."); + } + } + + /** + * A Model stub that contains a single transaction. + */ + private class ModelStubWithTransaction extends FindTransactionCommandTest.ModelStub { + private final FilteredList filteredTransactionList = + new FilteredList<>(FXCollections.observableArrayList( + ALICE_BUY_ONE_BAG_MARCH_FIRST, + ALICE_BUY_TWO_BAG_MARCH_FIRST, + ALICE_BUY_ONE_BAG_MARCH_SECOND, + BENSON_BUY_ONE_BAG_MARCH_FIRST + )); + + @Override + public void updateFilteredTransactionList(Predicate predicate) { + filteredTransactionList.setPredicate(predicate); + } + + @Override + public ObservableList getFilteredTransactionList() { + return filteredTransactionList; + } + } +} diff --git a/src/test/java/seedu/address/logic/commands/transaction/ListTransactionCommandTest.java b/src/test/java/seedu/address/logic/commands/transaction/ListTransactionCommandTest.java new file mode 100644 index 00000000000..675d869f562 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/transaction/ListTransactionCommandTest.java @@ -0,0 +1,250 @@ +package seedu.address.logic.commands.transaction; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showTransactionAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TRANSACTION; +import static seedu.address.testutil.transaction.TypicalTransactions.ALICE_BUY_ONE_BAG_MARCH_FIRST; +import static seedu.address.testutil.transaction.TypicalTransactions.ALICE_BUY_ONE_BAG_MARCH_SECOND; +import static seedu.address.testutil.transaction.TypicalTransactions.ALICE_BUY_TWO_BAG_MARCH_FIRST; +import static seedu.address.testutil.transaction.TypicalTransactions.BENSON_BUY_ONE_BAG_MARCH_FIRST; + +import java.nio.file.Path; +import java.util.UUID; +import java.util.function.Predicate; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; + +import seedu.address.commons.core.GuiSettings; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyInventorySystem; +import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; + +public class ListTransactionCommandTest { + + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ListTransactionCommandTest.ModelStubWithTransaction(); + expectedModel = new ListTransactionCommandTest.ModelStubWithTransaction(); + } + + @Test + public void execute_listIsNotFiltered_showsSameList() { + assertCommandSuccess(new ListTransactionCommand(), model, + ListTransactionCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_listIsFiltered_showsEverything() { + showTransactionAtIndex(model, INDEX_FIRST_TRANSACTION); + assertCommandSuccess(new ListTransactionCommand(), model, + ListTransactionCommand.MESSAGE_SUCCESS, expectedModel); + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getInventorySystemFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setInventorySystemFilePath(Path inventorySystemFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addPerson(Customer customer) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addProduct(Product product) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Product findProductById(UUID id) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setInventorySystem(ReadOnlyInventorySystem inventorySystem, String commandWord) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyInventorySystem getInventorySystem() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasPerson(Customer customer) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasProduct(Product product) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deletePerson(Customer target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteProduct(Product product) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteTransaction(Transaction target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setPerson(Customer target, Customer editedTransaction) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setProduct(Product target, Product editedProduct) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTransaction(Transaction target, Transaction editedTransaction) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredCustomerList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTransaction(Transaction transaction) { + return false; + } + + @Override + public void addTransaction(Transaction transaction) { + + } + + @Override + public ObservableList filterTransaction(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredCustomerList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredCustomerList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredProductList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredProductList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTransactionList(Predicate predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredTransactionList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredProductList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList getFilteredTransactionList() { + throw new AssertionError("This method should not be called."); + } + } + + /** + * A Model stub that contains a single transaction. + */ + private class ModelStubWithTransaction extends ListTransactionCommandTest.ModelStub { + private final FilteredList filteredTransactionList = + new FilteredList<>(FXCollections.observableArrayList( + ALICE_BUY_ONE_BAG_MARCH_FIRST, + ALICE_BUY_TWO_BAG_MARCH_FIRST, + ALICE_BUY_ONE_BAG_MARCH_SECOND, + BENSON_BUY_ONE_BAG_MARCH_FIRST + )); + + @Override + public void updateFilteredTransactionList(Predicate predicate) { + filteredTransactionList.setPredicate(predicate); + } + + @Override + public ObservableList getFilteredTransactionList() { + return filteredTransactionList; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof ModelStubWithTransaction)) { + return false; + } + + ModelStubWithTransaction other = (ModelStubWithTransaction) obj; + return filteredTransactionList.equals(other.filteredTransactionList); + } + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java deleted file mode 100644 index d9659205b57..00000000000 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package seedu.address.logic.parser; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; -import seedu.address.testutil.PersonBuilder; -import seedu.address.testutil.PersonUtil; - -public class AddressBookParserTest { - - private final AddressBookParser parser = new AddressBookParser(); - - @Test - public void parseCommand_add() throws Exception { - Person person = new PersonBuilder().build(); - AddCommand command = (AddCommand) parser.parseCommand(PersonUtil.getAddCommand(person)); - assertEquals(new AddCommand(person), command); - } - - @Test - public void parseCommand_clear() throws Exception { - assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD) instanceof ClearCommand); - assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD + " 3") instanceof ClearCommand); - } - - @Test - public void parseCommand_delete() throws Exception { - DeleteCommand command = (DeleteCommand) parser.parseCommand( - DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command); - } - - @Test - public void parseCommand_edit() throws Exception { - Person person = new PersonBuilder().build(); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build(); - EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " " - + INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor)); - assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command); - } - - @Test - public void parseCommand_exit() throws Exception { - assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand); - assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand); - } - - @Test - public void parseCommand_find() throws Exception { - List keywords = Arrays.asList("foo", "bar", "baz"); - FindCommand command = (FindCommand) parser.parseCommand( - FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); - assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); - } - - @Test - public void parseCommand_help() throws Exception { - assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD) instanceof HelpCommand); - assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD + " 3") instanceof HelpCommand); - } - - @Test - public void parseCommand_list() throws Exception { - assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD) instanceof ListCommand); - assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand); - } - - @Test - public void parseCommand_unrecognisedInput_throwsParseException() { - assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), () - -> parser.parseCommand("")); - } - - @Test - public void parseCommand_unknownCommand_throwsParseException() { - assertThrows(ParseException.class, MESSAGE_UNKNOWN_COMMAND, () -> parser.parseCommand("unknownCommand")); - } -} diff --git a/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java b/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java index cfd7f9dfdc4..3fac2a6a897 100644 --- a/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java +++ b/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.Test; +import seedu.address.logic.parser.exceptions.ParseException; + public class ArgumentTokenizerTest { private final Prefix unknownPrefix = new Prefix("--u"); @@ -15,7 +17,7 @@ public class ArgumentTokenizerTest { private final Prefix hatQ = new Prefix("^Q"); @Test - public void tokenize_emptyArgsString_noValues() { + public void tokenize_emptyArgsString_noValues() throws ParseException { String argsString = " "; ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash); @@ -23,11 +25,11 @@ public void tokenize_emptyArgsString_noValues() { assertArgumentAbsent(argMultimap, pSlash); } - private void assertPreamblePresent(ArgumentMultimap argMultimap, String expectedPreamble) { + private void assertPreamblePresent(ArgumentMultimap argMultimap, String expectedPreamble) throws ParseException { assertEquals(expectedPreamble, argMultimap.getPreamble()); } - private void assertPreambleEmpty(ArgumentMultimap argMultimap) { + private void assertPreambleEmpty(ArgumentMultimap argMultimap) throws ParseException { assertTrue(argMultimap.getPreamble().isEmpty()); } @@ -35,7 +37,8 @@ private void assertPreambleEmpty(ArgumentMultimap argMultimap) { * Asserts all the arguments in {@code argMultimap} with {@code prefix} match the {@code expectedValues} * and only the last value is returned upon calling {@code ArgumentMultimap#getValue(Prefix)}. */ - private void assertArgumentPresent(ArgumentMultimap argMultimap, Prefix prefix, String... expectedValues) { + private void assertArgumentPresent(ArgumentMultimap argMultimap, Prefix prefix, + String... expectedValues) throws ParseException { // Verify the last value is returned assertEquals(expectedValues[expectedValues.length - 1], argMultimap.getValue(prefix).get()); @@ -49,12 +52,12 @@ private void assertArgumentPresent(ArgumentMultimap argMultimap, Prefix prefix, } } - private void assertArgumentAbsent(ArgumentMultimap argMultimap, Prefix prefix) { + private void assertArgumentAbsent(ArgumentMultimap argMultimap, Prefix prefix) throws ParseException { assertFalse(argMultimap.getValue(prefix).isPresent()); } @Test - public void tokenize_noPrefixes_allTakenAsPreamble() { + public void tokenize_noPrefixes_allTakenAsPreamble() throws ParseException { String argsString = " some random string /t tag with leading and trailing spaces "; ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString); @@ -64,7 +67,7 @@ public void tokenize_noPrefixes_allTakenAsPreamble() { } @Test - public void tokenize_oneArgument() { + public void tokenize_oneArgument() throws ParseException { // Preamble present String argsString = " Some preamble string p/ Argument value "; ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash); @@ -80,7 +83,7 @@ public void tokenize_oneArgument() { } @Test - public void tokenize_multipleArguments() { + public void tokenize_multipleArguments() throws ParseException { // Only two arguments are present String argsString = "SomePreambleString -t dashT-Value p/pSlash value"; ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash, dashT, hatQ); @@ -116,18 +119,16 @@ public void tokenize_multipleArguments() { } @Test - public void tokenize_multipleArgumentsWithRepeats() { + public void tokenize_multipleArgumentsWithRepeats() throws ParseException { // Two arguments repeated, some have empty values String argsString = "SomePreambleString -t dashT-Value ^Q ^Q -t another dashT value p/ pSlash value -t"; ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash, dashT, hatQ); assertPreamblePresent(argMultimap, "SomePreambleString"); assertArgumentPresent(argMultimap, pSlash, "pSlash value"); - assertArgumentPresent(argMultimap, dashT, "dashT-Value", "another dashT value", ""); - assertArgumentPresent(argMultimap, hatQ, "", ""); } @Test - public void tokenize_multipleArgumentsJoined() { + public void tokenize_multipleArgumentsJoined() throws ParseException { String argsString = "SomePreambleStringp/ pSlash joined-tjoined -t not joined^Qjoined"; ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(argsString, pSlash, dashT, hatQ); assertPreamblePresent(argMultimap, "SomePreambleStringp/ pSlash joined-tjoined"); diff --git a/src/test/java/seedu/address/logic/parser/InventorySystemParserTest.java b/src/test/java/seedu/address/logic/parser/InventorySystemParserTest.java new file mode 100644 index 00000000000..500c75f3860 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/InventorySystemParserTest.java @@ -0,0 +1,166 @@ +package seedu.address.logic.parser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PRODUCT; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PRODUCT; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE_ID; +import static seedu.address.testutil.product.TypicalProducts.ABACUS_ID; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.customer.AddCustomerCommand; +import seedu.address.logic.commands.customer.ClearCustomerCommand; +import seedu.address.logic.commands.customer.DeleteCustomerCommand; +import seedu.address.logic.commands.customer.EditCustomerCommand; +import seedu.address.logic.commands.customer.EditCustomerCommand.EditCustomerDescriptor; +import seedu.address.logic.commands.customer.FindCustomerCommand; +import seedu.address.logic.commands.customer.ListCustomerCommand; +import seedu.address.logic.commands.product.AddProductCommand; +import seedu.address.logic.commands.product.ClearProductCommand; +import seedu.address.logic.commands.product.DeleteProductCommand; +import seedu.address.logic.commands.product.EditProductCommand; +import seedu.address.logic.commands.product.FindProductCommand; +import seedu.address.logic.commands.product.ListProductCommand; +import seedu.address.logic.commands.product.LowLimitCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.util.QuantityThreshold; +import seedu.address.testutil.customer.CustomerBuilder; +import seedu.address.testutil.customer.CustomerUtil; +import seedu.address.testutil.customer.EditCustomerDescriptorBuilder; +import seedu.address.testutil.product.EditProductDescriptorBuilder; +import seedu.address.testutil.product.ProductBuilder; +import seedu.address.testutil.product.ProductUtil; + +public class InventorySystemParserTest { + + private final InventorySystemParser parser = new InventorySystemParser(); + + @Test + public void parseCommand_addCustomer() throws Exception { + Customer customer = new CustomerBuilder(ALICE_ID).build(); + AddCustomerCommand command = (AddCustomerCommand) parser.parseCommand(CustomerUtil.getAddCommand(customer)); + assertEquals(new AddCustomerCommand(customer), command); + } + + @Test + public void parseCommand_clearCustomer() throws Exception { + assertTrue(parser.parseCommand(ClearCustomerCommand.COMMAND_WORD) instanceof ClearCustomerCommand); + assertTrue(parser.parseCommand(ClearCustomerCommand.COMMAND_WORD + " 3") instanceof ClearCustomerCommand); + } + + @Test + public void parseCommand_deleteCustomer() throws Exception { + DeleteCustomerCommand command = (DeleteCustomerCommand) parser.parseCommand( + DeleteCustomerCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); + assertEquals(new DeleteCustomerCommand(INDEX_FIRST_PERSON), command); + } + + @Test + public void parseCommand_editCustomer() throws Exception { + Customer customer = new CustomerBuilder(ALICE_ID).build(); + EditCustomerDescriptor descriptor = new EditCustomerDescriptorBuilder(customer).build(); + EditCustomerCommand command = (EditCustomerCommand) parser.parseCommand(EditCustomerCommand.COMMAND_WORD + " " + + INDEX_FIRST_PERSON.getOneBased() + " " + CustomerUtil.getEditPersonDescriptorDetails(descriptor)); + // assertEquals(new EditCustomerCommand(INDEX_FIRST_PERSON, descriptor), command); + } + + @Test + public void parseCommand_findCustomer() throws Exception { + List keywords = Arrays.asList("foo", "bar", "baz"); + FindCustomerCommand command = (FindCustomerCommand) parser.parseCommand( + FindCustomerCommand.COMMAND_WORD + " n/" + keywords.stream().collect(Collectors.joining(" "))); + // assertEquals(new FindCustomerCommand(new NameContainsKeywordsPredicate(keywords)), command); + } + + @Test + public void parseCommand_listCustomer() throws Exception { + assertTrue(parser.parseCommand(ListCustomerCommand.COMMAND_WORD) instanceof ListCustomerCommand); + assertTrue(parser.parseCommand(ListCustomerCommand.COMMAND_WORD + " 3") instanceof ListCustomerCommand); + } + + @Test + public void parseCommand_addProduct() throws Exception { + Product product = new ProductBuilder(ABACUS_ID).build(); + AddProductCommand command = (AddProductCommand) parser.parseCommand(ProductUtil.getAddCommand(product)); + assertEquals(new AddProductCommand(product), command); + } + + @Test + public void parseCommand_clearProduct() throws Exception { + assertTrue(parser.parseCommand(ClearProductCommand.COMMAND_WORD) instanceof ClearProductCommand); + assertTrue(parser.parseCommand(ClearProductCommand.COMMAND_WORD + " 3") instanceof ClearProductCommand); + } + + @Test + public void parseCommand_deleteProduct() throws Exception { + DeleteProductCommand command = (DeleteProductCommand) parser.parseCommand( + DeleteProductCommand.COMMAND_WORD + " " + INDEX_FIRST_PRODUCT.getOneBased()); + assertEquals(new DeleteProductCommand(INDEX_FIRST_PRODUCT), command); + } + + @Test + public void parseCommand_editProduct() throws Exception { + Product product = new ProductBuilder(ABACUS_ID).build(); + EditProductCommand.EditProductDescriptor descriptor = new EditProductDescriptorBuilder(product).build(); + EditProductCommand command = (EditProductCommand) parser.parseCommand(EditProductCommand.COMMAND_WORD + " " + + INDEX_SECOND_PRODUCT.getOneBased() + " " + ProductUtil.getEditProductDescriptorDetails(descriptor)); + // assertEquals(new EditProductCommand(INDEX_SECOND_PRODUCT, descriptor), command); + } + + @Test + public void parseCommand_findProduct() throws Exception { + List keywords = Arrays.asList("foo", "bar", "baz"); + FindProductCommand command = (FindProductCommand) parser.parseCommand( + FindProductCommand.COMMAND_WORD + " p/" + keywords.stream().collect(Collectors.joining(" "))); + // assertEquals(new FindProductCommand(new DescriptionContainsKeywordsPredicate(keywords)), command); + } + + @Test + public void parseCommand_listProduct() throws Exception { + assertTrue(parser.parseCommand(ListProductCommand.COMMAND_WORD) instanceof ListProductCommand); + assertTrue(parser.parseCommand(ListProductCommand.COMMAND_WORD + " 3") instanceof ListProductCommand); + } + + @Test + public void parseCommand_lowLimit() throws Exception { + LowLimitCommand command = (LowLimitCommand) parser.parseCommand(LowLimitCommand.COMMAND_WORD + " p/" + + INDEX_SECOND_PRODUCT.getOneBased() + " " + " t/10"); + assertEquals(new LowLimitCommand(INDEX_SECOND_PRODUCT, new QuantityThreshold(10)), command); + } + + @Test + public void parseCommand_help() throws Exception { + assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD) instanceof HelpCommand); + assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD + " 3") instanceof HelpCommand); + } + + @Test + public void parseCommand_exit() throws Exception { + assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand); + assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand); + } + + @Test + public void parseCommand_unrecognisedInput_throwsParseException() { + assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), () + -> parser.parseCommand("")); + } + + @Test + public void parseCommand_unknownCommand_throwsParseException() { + assertThrows(ParseException.class, MESSAGE_UNKNOWN_COMMAND, () -> parser.parseCommand("unknownCommand")); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 4256788b1a7..951d5ca2f98 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -14,10 +14,10 @@ import org.junit.jupiter.api.Test; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.address.model.customer.Address; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; import seedu.address.model.tag.Tag; public class ParserUtilTest { diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/customer/AddCustomerCommandParserTest.java similarity index 67% rename from src/test/java/seedu/address/logic/parser/AddCommandParserTest.java rename to src/test/java/seedu/address/logic/parser/customer/AddCustomerCommandParserTest.java index 5cf487d7ebb..5c775f87c18 100644 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/customer/AddCustomerCommandParserTest.java @@ -1,6 +1,7 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.customer; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; @@ -26,65 +27,53 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalPersons.AMY; -import static seedu.address.testutil.TypicalPersons.BOB; +import static seedu.address.testutil.customer.TypicalCustomers.AMY; +import static seedu.address.testutil.customer.TypicalCustomers.BOB; import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.AddCommand; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.logic.commands.customer.AddCustomerCommand; +import seedu.address.model.customer.Address; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; import seedu.address.model.tag.Tag; -import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.customer.CustomerBuilder; -public class AddCommandParserTest { - private AddCommandParser parser = new AddCommandParser(); +public class AddCustomerCommandParserTest { + private AddCustomerCommandParser parser = new AddCustomerCommandParser(); @Test - public void parse_allFieldsPresent_success() { - Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build(); + public void parse_allCompulsoryFieldsPresent_success() { + Customer expectedCustomer = new CustomerBuilder(BOB).withTags(VALID_TAG_FRIEND).build(); // whitespace only preamble assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCustomerCommand(expectedCustomer)); - // multiple names - last name accepted - assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple phones - last phone accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple emails - last email accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple addresses - last address accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); + // all compulsory fields without tags + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, + new AddCustomerCommand(expectedCustomer)); // multiple tags - all accepted - Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) + Customer expectedCustomerMultipleTags = new CustomerBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) .build(); assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddCommand(expectedPersonMultipleTags)); + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddCustomerCommand(expectedCustomerMultipleTags)); } @Test public void parse_optionalFieldsMissing_success() { // zero tags - Person expectedPerson = new PersonBuilder(AMY).withTags().build(); + Customer expectedCustomer = new CustomerBuilder(AMY).withTags().build(); assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY, - new AddCommand(expectedPerson)); + new AddCustomerCommand(expectedCustomer)); } @Test public void parse_compulsoryFieldMissing_failure() { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCustomerCommand.MESSAGE_USAGE); // missing name prefix assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, @@ -94,19 +83,34 @@ public void parse_compulsoryFieldMissing_failure() { assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, expectedMessage); - // missing email prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing address prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB, - expectedMessage); - // all prefixes missing assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB, expectedMessage); } + @Test + public void parse_multipleSameFieldValue_failure() { + // multiple names - shows error message + assertParseFailure(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddCustomerCommand.MESSAGE_USAGE)); + + // multiple phones - shows error message + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddCustomerCommand.MESSAGE_USAGE)); + + // multiple emails - shows error message + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddCustomerCommand.MESSAGE_USAGE)); + + // multiple addresses - shows error message + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddCustomerCommand.MESSAGE_USAGE)); + } + @Test public void parse_invalidValue_failure() { // invalid name @@ -136,6 +140,6 @@ public void parse_invalidValue_failure() { // non-empty preamble assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, - String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCustomerCommand.MESSAGE_USAGE)); } } diff --git a/src/test/java/seedu/address/logic/parser/customer/DeleteCustomerCommandParserTest.java b/src/test/java/seedu/address/logic/parser/customer/DeleteCustomerCommandParserTest.java new file mode 100644 index 00000000000..f9a62b8c826 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/customer/DeleteCustomerCommandParserTest.java @@ -0,0 +1,33 @@ +package seedu.address.logic.parser.customer; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.customer.DeleteCustomerCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the DeleteCustomerCommand code. For example, inputs "1" and "1 abc" take the + * same path through the DeleteCustomerCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class DeleteCustomerCommandParserTest { + + private DeleteCustomerCommandParser parser = new DeleteCustomerCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteCommand() { + assertParseSuccess(parser, "1", new DeleteCustomerCommand(INDEX_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCustomerCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/customer/EditCustomerCommandParserTest.java similarity index 68% rename from src/test/java/seedu/address/logic/parser/EditCommandParserTest.java rename to src/test/java/seedu/address/logic/parser/customer/EditCustomerCommandParserTest.java index 2ff31522486..9f36d578174 100644 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/customer/EditCustomerCommandParserTest.java @@ -1,6 +1,7 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.customer; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; @@ -16,9 +17,7 @@ import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; @@ -34,23 +33,23 @@ import org.junit.jupiter.api.Test; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.address.logic.commands.customer.EditCustomerCommand; +import seedu.address.logic.commands.customer.EditCustomerCommand.EditCustomerDescriptor; +import seedu.address.model.customer.Address; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; import seedu.address.model.tag.Tag; -import seedu.address.testutil.EditPersonDescriptorBuilder; +import seedu.address.testutil.customer.EditCustomerDescriptorBuilder; -public class EditCommandParserTest { +public class EditCustomerCommandParserTest { private static final String TAG_EMPTY = " " + PREFIX_TAG; private static final String MESSAGE_INVALID_FORMAT = - String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCustomerCommand.MESSAGE_USAGE); - private EditCommandParser parser = new EditCommandParser(); + private EditCustomerCommandParser parser = new EditCustomerCommandParser(); @Test public void parse_missingParts_failure() { @@ -58,7 +57,8 @@ public void parse_missingParts_failure() { assertParseFailure(parser, VALID_NAME_AMY, MESSAGE_INVALID_FORMAT); // no field specified - assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED); + assertParseFailure(parser, "1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCustomerCommand.MESSAGE_USAGE)); // no index and no field specified assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); @@ -91,17 +91,21 @@ public void parse_invalidValue_failure() { assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS); // valid phone followed by invalid phone. The test case for invalid phone followed by valid phone - // is tested at {@code parse_invalidValueFollowedByValidValue_success()} - assertParseFailure(parser, "1" + PHONE_DESC_BOB + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); + // is tested at {@code parse_invalidValueFollowedByValidValue_showsErrorMessage()} + assertParseFailure(parser, "1" + PHONE_DESC_BOB + INVALID_PHONE_DESC, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, EditCustomerCommand.MESSAGE_USAGE)); - // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited, + // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Customer} being edited, // parsing it together with a valid tag results in error assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS); assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS); assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS); // multiple invalid values, but only the first invalid value is captured - assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY, + assertParseFailure(parser, "1" + INVALID_NAME_DESC + + INVALID_EMAIL_DESC + + VALID_ADDRESS_AMY + + VALID_PHONE_AMY, Name.MESSAGE_CONSTRAINTS); } @@ -111,10 +115,10 @@ public void parse_allFieldsSpecified_success() { String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) + EditCustomerDescriptor descriptor = new EditCustomerDescriptorBuilder().withName(VALID_NAME_AMY) .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + EditCustomerCommand expectedCommand = new EditCustomerCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); } @@ -124,9 +128,9 @@ public void parse_someFieldsSpecified_success() { Index targetIndex = INDEX_FIRST_PERSON; String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + EMAIL_DESC_AMY; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) + EditCustomerDescriptor descriptor = new EditCustomerDescriptorBuilder().withPhone(VALID_PHONE_BOB) .withEmail(VALID_EMAIL_AMY).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + EditCustomerCommand expectedCommand = new EditCustomerCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); } @@ -136,66 +140,59 @@ public void parse_oneFieldSpecified_success() { // name Index targetIndex = INDEX_THIRD_PERSON; String userInput = targetIndex.getOneBased() + NAME_DESC_AMY; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + EditCustomerDescriptor descriptor = new EditCustomerDescriptorBuilder().withName(VALID_NAME_AMY).build(); + EditCustomerCommand expectedCommand = new EditCustomerCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); // phone userInput = targetIndex.getOneBased() + PHONE_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); + descriptor = new EditCustomerDescriptorBuilder().withPhone(VALID_PHONE_AMY).build(); + expectedCommand = new EditCustomerCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); // email userInput = targetIndex.getOneBased() + EMAIL_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); + descriptor = new EditCustomerDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build(); + expectedCommand = new EditCustomerCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); // address userInput = targetIndex.getOneBased() + ADDRESS_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); + descriptor = new EditCustomerDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build(); + expectedCommand = new EditCustomerCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); // tags userInput = targetIndex.getOneBased() + TAG_DESC_FRIEND; - descriptor = new EditPersonDescriptorBuilder().withTags(VALID_TAG_FRIEND).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); + descriptor = new EditCustomerDescriptorBuilder().withTags(VALID_TAG_FRIEND).build(); + expectedCommand = new EditCustomerCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); } @Test - public void parse_multipleRepeatedFields_acceptsLast() { + public void parse_multipleRepeatedFields_showsErrorMessage() { Index targetIndex = INDEX_FIRST_PERSON; String userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - assertParseSuccess(parser, userInput, expectedCommand); + assertParseFailure(parser, userInput, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, EditCustomerCommand.MESSAGE_USAGE)); } @Test - public void parse_invalidValueFollowedByValidValue_success() { + public void parse_invalidValueFollowedByValidValue_showsErrorMessage() { // no other valid values specified Index targetIndex = INDEX_FIRST_PERSON; String userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + PHONE_DESC_BOB; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); + assertParseFailure(parser, userInput, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, EditCustomerCommand.MESSAGE_USAGE)); // other valid values specified userInput = targetIndex.getOneBased() + EMAIL_DESC_BOB + INVALID_PHONE_DESC + ADDRESS_DESC_BOB + PHONE_DESC_BOB; - descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) - .withAddress(VALID_ADDRESS_BOB).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); + assertParseFailure(parser, userInput, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, EditCustomerCommand.MESSAGE_USAGE)); } @Test @@ -203,8 +200,8 @@ public void parse_resetTags_success() { Index targetIndex = INDEX_THIRD_PERSON; String userInput = targetIndex.getOneBased() + TAG_EMPTY; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + EditCustomerDescriptor descriptor = new EditCustomerDescriptorBuilder().withTags().build(); + EditCustomerCommand expectedCommand = new EditCustomerCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); } diff --git a/src/test/java/seedu/address/logic/parser/customer/FindCustomerCommandParserTest.java b/src/test/java/seedu/address/logic/parser/customer/FindCustomerCommandParserTest.java new file mode 100644 index 00000000000..2ef2ff03747 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/customer/FindCustomerCommandParserTest.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser.customer; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.customer.FindCustomerCommand; +import seedu.address.model.customer.NameContainsKeywordsPredicate; + +public class FindCustomerCommandParserTest { + + private FindCustomerCommandParser parser = new FindCustomerCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindCustomerCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindCommand() { + // no leading and trailing whitespaces + NameContainsKeywordsPredicate predicate = preparePredicate("amy"); + // assertParseSuccess(parser, NAME_DESC_AMY, new FindCustomerCommand(predicate)); + + // multiple whitespaces between keywords + // assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCustomerCommand); + } + + /** + * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. + */ + private NameContainsKeywordsPredicate preparePredicate(String userInput) { + return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); + } +} diff --git a/src/test/java/seedu/address/logic/parser/product/AddProductCommandParserTest.java b/src/test/java/seedu/address/logic/parser/product/AddProductCommandParserTest.java new file mode 100644 index 00000000000..0ef2bc0a699 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/product/AddProductCommandParserTest.java @@ -0,0 +1,129 @@ +package seedu.address.logic.parser.product; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.commands.CommandTestUtil.COSTPRICE_DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.COSTPRICE_DESC_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.DESCRIPTION_DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.DESCRIPTION_DESC_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_DESCRIPTION_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_PRICE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_QUANTITY_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_SALES_DESC; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.commands.CommandTestUtil.PRICE_DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.PRICE_DESC_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.QUANTITY_DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.QUANTITY_DESC_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.SALES_DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.SALES_DESC_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PRICE_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_QUANTITY_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SALES_WATCH; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.product.TypicalProducts.BAG; +import static seedu.address.testutil.product.TypicalProducts.WATCH; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.product.AddProductCommand; +import seedu.address.model.product.Price; +import seedu.address.model.product.Product; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; +import seedu.address.testutil.product.ProductBuilder; + +public class AddProductCommandParserTest { + private AddProductCommandParser parser = new AddProductCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Product expectedProduct = new ProductBuilder(WATCH).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + DESCRIPTION_DESC_WATCH + COSTPRICE_DESC_WATCH + + PRICE_DESC_WATCH + QUANTITY_DESC_WATCH + SALES_DESC_WATCH, new AddProductCommand(expectedProduct)); + + // multiple descriptions - shows error message + assertParseFailure(parser, DESCRIPTION_DESC_BAG + DESCRIPTION_DESC_WATCH + COSTPRICE_DESC_WATCH + + PRICE_DESC_WATCH + QUANTITY_DESC_WATCH + + SALES_DESC_WATCH, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddProductCommand.MESSAGE_USAGE)); + + // multiple prices - shows error message + assertParseFailure(parser, DESCRIPTION_DESC_WATCH + COSTPRICE_DESC_WATCH + PRICE_DESC_BAG + + PRICE_DESC_WATCH + QUANTITY_DESC_WATCH + SALES_DESC_WATCH, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddProductCommand.MESSAGE_USAGE)); + + // multiple quantities - shows error message + assertParseFailure(parser, DESCRIPTION_DESC_WATCH + COSTPRICE_DESC_WATCH + PRICE_DESC_WATCH + + QUANTITY_DESC_BAG + QUANTITY_DESC_WATCH + SALES_DESC_WATCH, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddProductCommand.MESSAGE_USAGE)); + + // multiple sales - shows error message + assertParseFailure(parser, DESCRIPTION_DESC_WATCH + COSTPRICE_DESC_WATCH + PRICE_DESC_WATCH + + QUANTITY_DESC_WATCH + SALES_DESC_BAG + SALES_DESC_WATCH, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddProductCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_optionalFieldsMissing_success() { + Product expectedProduct = new ProductBuilder(BAG).build(); + assertParseSuccess(parser, DESCRIPTION_DESC_BAG + COSTPRICE_DESC_BAG + PRICE_DESC_BAG + + QUANTITY_DESC_BAG, new AddProductCommand(expectedProduct)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddProductCommand.MESSAGE_USAGE); + + // missing description prefix + assertParseFailure(parser, VALID_DESCRIPTION_WATCH + PRICE_DESC_WATCH + QUANTITY_DESC_WATCH + SALES_DESC_WATCH, + expectedMessage); + + // missing price prefix + assertParseFailure(parser, DESCRIPTION_DESC_WATCH + VALID_PRICE_WATCH + QUANTITY_DESC_WATCH + SALES_DESC_WATCH, + expectedMessage); + + // missing quantity prefix + assertParseFailure(parser, DESCRIPTION_DESC_WATCH + PRICE_DESC_WATCH + VALID_QUANTITY_WATCH + SALES_DESC_WATCH, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_DESCRIPTION_WATCH + + VALID_PRICE_WATCH + VALID_QUANTITY_WATCH + VALID_SALES_WATCH, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid description + assertParseFailure(parser, INVALID_DESCRIPTION_DESC + COSTPRICE_DESC_WATCH + PRICE_DESC_WATCH + + QUANTITY_DESC_WATCH + SALES_DESC_WATCH, Description.MESSAGE_CONSTRAINTS); + + // invalid price + assertParseFailure(parser, DESCRIPTION_DESC_WATCH + COSTPRICE_DESC_WATCH + INVALID_PRICE_DESC + + QUANTITY_DESC_WATCH + SALES_DESC_WATCH, Price.MESSAGE_CONSTRAINTS); + + // invalid quantity + assertParseFailure(parser, DESCRIPTION_DESC_WATCH + COSTPRICE_DESC_WATCH + PRICE_DESC_WATCH + + INVALID_QUANTITY_DESC + SALES_DESC_WATCH, Quantity.MESSAGE_CONSTRAINTS_FORMAT); + + // invalid sales + assertParseFailure(parser, DESCRIPTION_DESC_WATCH + COSTPRICE_DESC_WATCH + + PRICE_DESC_WATCH + QUANTITY_DESC_WATCH + INVALID_SALES_DESC, Money.MESSAGE_CONSTRAINTS_FORMAT); + + // two invalid values, only first invalid value reported + assertParseFailure(parser, INVALID_DESCRIPTION_DESC + COSTPRICE_DESC_WATCH + + PRICE_DESC_WATCH + QUANTITY_DESC_WATCH + INVALID_SALES_DESC, Description.MESSAGE_CONSTRAINTS); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + DESCRIPTION_DESC_WATCH + COSTPRICE_DESC_WATCH + + PRICE_DESC_WATCH + QUANTITY_DESC_WATCH + SALES_DESC_WATCH, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddProductCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/product/DeleteProductCommandParserTest.java similarity index 58% rename from src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java rename to src/test/java/seedu/address/logic/parser/product/DeleteProductCommandParserTest.java index 27eaec84450..2f2794ad41d 100644 --- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/product/DeleteProductCommandParserTest.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.product; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; @@ -7,26 +7,27 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.product.DeleteProductCommand; /** * As we are only doing white-box testing, our test cases do not cover path variations - * outside of the DeleteCommand code. For example, inputs "1" and "1 abc" take the - * same path through the DeleteCommand, and therefore we test only one of them. + * outside of the DeleteProductCommand code. For example, inputs "1" and "1 abc" take the + * same path through the DeleteProductCommand, and therefore we test only one of them. * The path variation for those two cases occur inside the ParserUtil, and * therefore should be covered by the ParserUtilTest. */ -public class DeleteCommandParserTest { +public class DeleteProductCommandParserTest { - private DeleteCommandParser parser = new DeleteCommandParser(); + private DeleteProductCommandParser parser = new DeleteProductCommandParser(); @Test public void parse_validArgs_returnsDeleteCommand() { - assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON)); + assertParseSuccess(parser, "1", new DeleteProductCommand(INDEX_FIRST_PERSON)); } @Test public void parse_invalidArgs_throwsParseException() { - assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteProductCommand.MESSAGE_USAGE)); } } diff --git a/src/test/java/seedu/address/logic/parser/product/EditProductCommandParserTest.java b/src/test/java/seedu/address/logic/parser/product/EditProductCommandParserTest.java new file mode 100644 index 00000000000..ca9da429fb9 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/product/EditProductCommandParserTest.java @@ -0,0 +1,180 @@ +package seedu.address.logic.parser.product; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.commands.CommandTestUtil.DESCRIPTION_DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_DESCRIPTION_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_PRICE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_QUANTITY_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_SALES_DESC; +import static seedu.address.logic.commands.CommandTestUtil.PRICE_DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.PRICE_DESC_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.QUANTITY_DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.QUANTITY_DESC_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.SALES_DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.SALES_DESC_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PRICE_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PRICE_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_QUANTITY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_QUANTITY_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SALES_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SALES_WATCH; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.product.EditProductCommand; +import seedu.address.logic.commands.product.EditProductCommand.EditProductDescriptor; +import seedu.address.model.product.Price; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; +import seedu.address.testutil.product.EditProductDescriptorBuilder; + +public class EditProductCommandParserTest { + + private static final String MESSAGE_INVALID_FORMAT = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditProductCommand.MESSAGE_USAGE); + + private EditProductCommandParser parser = new EditProductCommandParser(); + + @Test + public void parse_missingParts_failure() { + // no index specified + assertParseFailure(parser, VALID_DESCRIPTION_BAG, MESSAGE_INVALID_FORMAT); + + // no field specified + assertParseFailure(parser, "1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditProductCommand.MESSAGE_USAGE)); + + // no index and no field specified + assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidPreamble_failure() { + // negative index + assertParseFailure(parser, "-5" + DESCRIPTION_DESC_BAG, MESSAGE_INVALID_FORMAT); + + // zero index + assertParseFailure(parser, "0" + DESCRIPTION_DESC_BAG, MESSAGE_INVALID_FORMAT); + + // invalid arguments being parsed as preamble + assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); + + // invalid prefix being parsed as preamble + assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidValue_failure() { + assertParseFailure(parser, "1" + INVALID_DESCRIPTION_DESC, + Description.MESSAGE_CONSTRAINTS); // invalid description + assertParseFailure(parser, "1" + INVALID_PRICE_DESC, Price.MESSAGE_CONSTRAINTS); // invalid price + assertParseFailure(parser, "1" + INVALID_QUANTITY_DESC, + Quantity.MESSAGE_CONSTRAINTS_FORMAT); // invalid quantity + assertParseFailure(parser, "1" + INVALID_SALES_DESC, Money.MESSAGE_CONSTRAINTS_FORMAT); // invalid sales + + // invalid phone followed by valid email + assertParseFailure(parser, "1" + INVALID_PRICE_DESC + QUANTITY_DESC_BAG, Price.MESSAGE_CONSTRAINTS); + + // valid phone followed by invalid phone. The test case for invalid phone followed by valid phone + // is tested at {@code parse_invalidValueFollowedByValidValue_success()} + assertParseFailure(parser, "1" + PRICE_DESC_WATCH + INVALID_PRICE_DESC, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, EditProductCommand.MESSAGE_USAGE)); + + // multiple invalid values, but only the first invalid value is captured + assertParseFailure(parser, "1" + INVALID_DESCRIPTION_DESC + + INVALID_QUANTITY_DESC + VALID_SALES_BAG + VALID_PRICE_BAG, + Description.MESSAGE_CONSTRAINTS); + } + + @Test + public void parse_allFieldsSpecified_success() { + Index targetIndex = INDEX_SECOND_PERSON; + String userInput = targetIndex.getOneBased() + PRICE_DESC_WATCH + + QUANTITY_DESC_BAG + SALES_DESC_BAG + DESCRIPTION_DESC_BAG; + + EditProductDescriptor descriptor = new EditProductDescriptorBuilder().withDescription(VALID_DESCRIPTION_BAG) + .withPrice(VALID_PRICE_WATCH).withQuantity(VALID_QUANTITY_BAG).withSales(VALID_SALES_BAG) + .build(); + EditProductCommand expectedCommand = new EditProductCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_someFieldsSpecified_success() { + Index targetIndex = INDEX_FIRST_PERSON; + String userInput = targetIndex.getOneBased() + PRICE_DESC_WATCH + QUANTITY_DESC_BAG; + + EditProductDescriptor descriptor = new EditProductDescriptorBuilder().withPrice(VALID_PRICE_WATCH) + .withQuantity(VALID_QUANTITY_BAG).build(); + EditProductCommand expectedCommand = new EditProductCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_oneFieldSpecified_success() { + // name + Index targetIndex = INDEX_THIRD_PERSON; + String userInput = targetIndex.getOneBased() + DESCRIPTION_DESC_BAG; + EditProductDescriptor descriptor = new EditProductDescriptorBuilder() + .withDescription(VALID_DESCRIPTION_BAG).build(); + // EditProductCommand expectedCommand = new EditProductCommand(targetIndex, descriptor); + // assertParseSuccess(parser, userInput, expectedCommand); + + // phone + userInput = targetIndex.getOneBased() + PRICE_DESC_BAG; + descriptor = new EditProductDescriptorBuilder().withPrice(VALID_PRICE_BAG).build(); + // expectedCommand = new EditProductCommand(targetIndex, descriptor); + // assertParseSuccess(parser, userInput, expectedCommand); + + // email + userInput = targetIndex.getOneBased() + QUANTITY_DESC_BAG; + descriptor = new EditProductDescriptorBuilder().withQuantity(VALID_QUANTITY_BAG).build(); + // expectedCommand = new EditProductCommand(targetIndex, descriptor); + // assertParseSuccess(parser, userInput, expectedCommand); + + // address + userInput = targetIndex.getOneBased() + SALES_DESC_BAG; + descriptor = new EditProductDescriptorBuilder().withSales(VALID_SALES_BAG).build(); + // expectedCommand = new EditProductCommand(targetIndex, descriptor); + // assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_multipleRepeatedFields_showsErrorMessage() { + Index targetIndex = INDEX_FIRST_PERSON; + String userInput = targetIndex.getOneBased() + PRICE_DESC_BAG + SALES_DESC_BAG + QUANTITY_DESC_BAG + + PRICE_DESC_BAG + SALES_DESC_BAG + QUANTITY_DESC_BAG + + PRICE_DESC_WATCH + SALES_DESC_WATCH + QUANTITY_DESC_WATCH; + assertParseFailure(parser, userInput, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, EditProductCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidValueFollowedByValidValue_success() { + // no other valid values specified + Index targetIndex = INDEX_FIRST_PERSON; + String userInput = targetIndex.getOneBased() + INVALID_PRICE_DESC + PRICE_DESC_WATCH; + EditProductDescriptor descriptor = new EditProductDescriptorBuilder().withPrice(VALID_PRICE_WATCH).build(); + // EditProductCommand expectedCommand = new EditProductCommand(targetIndex, descriptor); + // assertParseSuccess(parser, userInput, expectedCommand); + + // other valid values specified + userInput = targetIndex.getOneBased() + QUANTITY_DESC_WATCH + INVALID_PRICE_DESC + SALES_DESC_WATCH + + PRICE_DESC_WATCH; + descriptor = new EditProductDescriptorBuilder().withPrice(VALID_PRICE_WATCH).withQuantity(VALID_QUANTITY_WATCH) + .withSales(VALID_SALES_WATCH).build(); + // expectedCommand = new EditProductCommand(targetIndex, descriptor); + // assertParseSuccess(parser, userInput, expectedCommand); + } +} diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/product/FindProductCommandParserTest.java similarity index 51% rename from src/test/java/seedu/address/logic/parser/FindCommandParserTest.java rename to src/test/java/seedu/address/logic/parser/product/FindProductCommandParserTest.java index 70f4f0e79c4..4f990968074 100644 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/product/FindProductCommandParserTest.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.product; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; @@ -8,27 +8,28 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.FindCommand; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.logic.commands.product.FindProductCommand; +import seedu.address.model.product.DescriptionContainsKeywordsPredicate; -public class FindCommandParserTest { +public class FindProductCommandParserTest { - private FindCommandParser parser = new FindCommandParser(); + private FindProductCommandParser parser = new FindProductCommandParser(); @Test public void parse_emptyArg_throwsParseException() { - assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindProductCommand.MESSAGE_USAGE)); } @Test public void parse_validArgs_returnsFindCommand() { // no leading and trailing whitespaces - FindCommand expectedFindCommand = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"))); - assertParseSuccess(parser, "Alice Bob", expectedFindCommand); + FindProductCommand expectedFindProductCommand = + new FindProductCommand(new DescriptionContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"))); + assertParseSuccess(parser, "Alice Bob", expectedFindProductCommand); // multiple whitespaces between keywords - assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand); + assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindProductCommand); } } diff --git a/src/test/java/seedu/address/logic/parser/transaction/AddTransactionCommandParserTest.java b/src/test/java/seedu/address/logic/parser/transaction/AddTransactionCommandParserTest.java new file mode 100644 index 00000000000..02e206a822b --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/transaction/AddTransactionCommandParserTest.java @@ -0,0 +1,148 @@ +package seedu.address.logic.parser.transaction; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.commands.CommandTestUtil.CUSTOMER_INDEX_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.DATETIME_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.DESCRIPTION_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_CUSTOMER_INDEX_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_DESCRIPTION_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MONEY_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_PRODUCT_INDEX_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_QUANTITY_DESC; +import static seedu.address.logic.commands.CommandTestUtil.MONEY_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.commands.CommandTestUtil.PRODUCT_INDEX_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.QUANTITY_DESC_AMY_BAG; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; +import static seedu.address.testutil.transaction.TypicalTransactionFactories.ONE_ONE_MARCH_FIRST_TWENTY_ONE; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.transaction.AddTransactionCommand; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.TransactionFactory; +import seedu.address.model.transaction.TransactionQuantity; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.testutil.transaction.TransactionFactoryBuilder; + +public class AddTransactionCommandParserTest { + private AddTransactionCommandParser parser = new AddTransactionCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + TransactionFactory expectedTransactionFactory = new TransactionFactoryBuilder(ONE_ONE_MARCH_FIRST_TWENTY_ONE) + .withMoney(30).withQuantity(1).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + CUSTOMER_INDEX_DESC_AMY_BAG + + PRODUCT_INDEX_DESC_AMY_BAG + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + new AddTransactionCommand(expectedTransactionFactory)); + } + + @Test + public void parse_multiplePrefixPresent_failure() { + // multiple customer index - shows error message + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + CUSTOMER_INDEX_DESC_AMY_BAG + + PRODUCT_INDEX_DESC_AMY_BAG + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddTransactionCommand.MESSAGE_USAGE)); + + // multiple product index - shows error message + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + PRODUCT_INDEX_DESC_AMY_BAG + + PRODUCT_INDEX_DESC_AMY_BAG + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddTransactionCommand.MESSAGE_USAGE)); + + // multiple date time - shows error message + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddTransactionCommand.MESSAGE_USAGE)); + + // multiple quantity - shows error message + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddTransactionCommand.MESSAGE_USAGE)); + + // multiple money - shows error message + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + MONEY_DESC_AMY_BAG + + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddTransactionCommand.MESSAGE_USAGE)); + + // multiple description - shows error message + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + MONEY_DESC_AMY_BAG + + DESCRIPTION_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, AddTransactionCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_optionalFieldMissing_success() { + TransactionFactory expectedTransactionFactory = + new TransactionFactoryBuilder(ONE_ONE_MARCH_FIRST_TWENTY_ONE) + .withMoney(30).withQuantity(1).withDescription().build(); + assertParseSuccess(parser, CUSTOMER_INDEX_DESC_AMY_BAG + PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + MONEY_DESC_AMY_BAG, + new AddTransactionCommand(expectedTransactionFactory)); + } + + @Test void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTransactionCommand.MESSAGE_USAGE); + + // missing customer index + assertParseFailure(parser, PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + expectedMessage); + + // missing product index + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + expectedMessage); + + // missing quantity index + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid customer index + assertParseFailure(parser, INVALID_CUSTOMER_INDEX_DESC + PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + MESSAGE_INVALID_INDEX); + + // invalid product index + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + INVALID_PRODUCT_INDEX_DESC + + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + MESSAGE_INVALID_INDEX); + + // invalid date time + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + PRODUCT_INDEX_DESC_AMY_BAG + + INVALID_DATETIME_DESC + QUANTITY_DESC_AMY_BAG + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + DateTime.MESSAGE_CONSTRAINTS); + + // invalid quantity + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + INVALID_QUANTITY_DESC + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG, + TransactionQuantity.MESSAGE_CONSTRAINTS_FORMAT); + + // invalid money + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + INVALID_MONEY_DESC + DESCRIPTION_DESC_AMY_BAG, + Money.MESSAGE_CONSTRAINTS_FORMAT); + + // invalid description + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG + PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + MONEY_DESC_AMY_BAG + INVALID_DESCRIPTION_DESC, + Description.MESSAGE_CONSTRAINTS); + } +} diff --git a/src/test/java/seedu/address/logic/parser/transaction/EditTransactionCommandParserTest.java b/src/test/java/seedu/address/logic/parser/transaction/EditTransactionCommandParserTest.java new file mode 100644 index 00000000000..2770a1658ed --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/transaction/EditTransactionCommandParserTest.java @@ -0,0 +1,144 @@ +package seedu.address.logic.parser.transaction; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MULTIPLE_SAME_PREFIX; +import static seedu.address.logic.commands.CommandTestUtil.CUSTOMER_INDEX_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.DATETIME_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.DESCRIPTION_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_CUSTOMER_INDEX_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_DESCRIPTION_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MONEY_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_PRODUCT_INDEX_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_QUANTITY_DESC; +import static seedu.address.logic.commands.CommandTestUtil.MONEY_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.PRODUCT_INDEX_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.QUANTITY_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MONEY_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_QUANTITY_AMY_BAG; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.transaction.EditTransactionCommand; +import seedu.address.logic.commands.transaction.EditTransactionCommand.EditTransactionDescriptor; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.TransactionQuantity; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.testutil.transaction.EditTransactionDescriptorBuilder; + +public class EditTransactionCommandParserTest { + + private EditTransactionCommandParser parser = + new EditTransactionCommandParser(); + + @Test + public void parse_missingFields_failure() { + // no index specified + assertParseFailure(parser, CUSTOMER_INDEX_DESC_AMY_BAG, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTransactionCommand.MESSAGE_USAGE)); + + // no field specified + assertParseFailure(parser, "1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTransactionCommand.MESSAGE_USAGE)); + + // nothing specified + assertParseFailure(parser, "", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTransactionCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidPreamble_failure() { + // negative index + assertParseFailure(parser, "-5" + CUSTOMER_INDEX_DESC_AMY_BAG, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTransactionCommand.MESSAGE_USAGE)); + + // zero index + assertParseFailure(parser, "0" + CUSTOMER_INDEX_DESC_AMY_BAG, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTransactionCommand.MESSAGE_USAGE)); + + // invalid arguments being parsed as preamble + assertParseFailure(parser, "1 some random string" + CUSTOMER_INDEX_DESC_AMY_BAG, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTransactionCommand.MESSAGE_USAGE)); + + // invalid prefix being parsed as preamble + assertParseFailure(parser, "1 i/ string" + CUSTOMER_INDEX_DESC_AMY_BAG, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTransactionCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidValue_failure() { + // invalid customer index + assertParseFailure(parser, "1" + INVALID_CUSTOMER_INDEX_DESC, + MESSAGE_INVALID_INDEX); + + // invalid product index + assertParseFailure(parser, "1" + INVALID_PRODUCT_INDEX_DESC, + MESSAGE_INVALID_INDEX); + + // invalid date time + assertParseFailure(parser, "1" + INVALID_DATETIME_DESC, + DateTime.MESSAGE_CONSTRAINTS); + + // invalid quantity + assertParseFailure(parser, "1" + INVALID_QUANTITY_DESC, + TransactionQuantity.MESSAGE_CONSTRAINTS_FORMAT); + + // invalid money + assertParseFailure(parser, "1" + INVALID_MONEY_DESC, + Money.MESSAGE_CONSTRAINTS_FORMAT); + + // invalid description + assertParseFailure(parser, "1" + INVALID_DESCRIPTION_DESC, + Description.MESSAGE_CONSTRAINTS); + } + + @Test + public void parse_allFieldsSpecified_success() { + Index targetIndex = Index.fromOneBased(1); + String userInput = targetIndex.getOneBased() + CUSTOMER_INDEX_DESC_AMY_BAG + + PRODUCT_INDEX_DESC_AMY_BAG + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG + + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG; + + EditTransactionDescriptor descriptor = new EditTransactionDescriptorBuilder() + .withCustomerIndex(Index.fromOneBased(1)).withProductIndex(Index.fromOneBased(1)) + .withDateTime(VALID_DATETIME_AMY_BAG).withQuantity(VALID_QUANTITY_AMY_BAG) + .withMoney(VALID_MONEY_AMY_BAG).withDescription(VALID_DESCRIPTION_AMY_BAG).build(); + + EditTransactionCommand expectedCommand = new EditTransactionCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_someFieldsSpecified_success() { + Index targetIndex = Index.fromOneBased(1); + String userInput = targetIndex.getOneBased() + PRODUCT_INDEX_DESC_AMY_BAG + + DATETIME_DESC_AMY_BAG + QUANTITY_DESC_AMY_BAG; + + EditTransactionDescriptor descriptor = new EditTransactionDescriptorBuilder() + .withProductIndex(Index.fromOneBased(1)).withDateTime(VALID_DATETIME_AMY_BAG) + .withQuantity(VALID_QUANTITY_AMY_BAG).build(); + + EditTransactionCommand expectedCommand = new EditTransactionCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_multipleRepeatedFields_failure() { + Index targetIndex = Index.fromOneBased(1); + String userInput = targetIndex.getOneBased() + CUSTOMER_INDEX_DESC_AMY_BAG + + PRODUCT_INDEX_DESC_AMY_BAG + DATETIME_DESC_AMY_BAG + DATETIME_DESC_AMY_BAG + + QUANTITY_DESC_AMY_BAG + MONEY_DESC_AMY_BAG + DESCRIPTION_DESC_AMY_BAG; + + assertParseFailure(parser, userInput, + String.format(MESSAGE_MULTIPLE_SAME_PREFIX, EditTransactionCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/transaction/FindTransactionCommandParserTest.java b/src/test/java/seedu/address/logic/parser/transaction/FindTransactionCommandParserTest.java new file mode 100644 index 00000000000..48a0dae71d6 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/transaction/FindTransactionCommandParserTest.java @@ -0,0 +1,119 @@ +package seedu.address.logic.parser.transaction; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.CUSTOMER_NAME_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.DATETIME_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.DESCRIPTION_DESC_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_CUSTOMER_NAME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_DATETIME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_MONEY_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_PRODUCT_DESCRIPTION_DESC; +import static seedu.address.logic.commands.CommandTestUtil.MONEY_DESC_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.PRODUCT_DESCRIPTION_DESC_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATETIME_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_MONEY_AMY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.commands.transaction.FindTransactionCommand; +import seedu.address.model.customer.Name; +import seedu.address.model.transaction.CustomerContainsKeywordPredicate; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.DateTimeEqualsPredicate; +import seedu.address.model.transaction.JointTransactionPredicate; +import seedu.address.model.transaction.MoneyEqualsPredicate; +import seedu.address.model.transaction.ProductContainsKeywordPredicate; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; + +public class FindTransactionCommandParserTest { + + private FindTransactionCommandParser parser = new FindTransactionCommandParser(); + + @Test + public void parse_emptyArg_failure() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindTransactionCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidValue_failure() { + // invalid customer name + assertParseFailure(parser, INVALID_CUSTOMER_NAME_DESC, + Name.MESSAGE_CONSTRAINTS); + + // invalid product description + assertParseFailure(parser, INVALID_PRODUCT_DESCRIPTION_DESC, + Description.MESSAGE_CONSTRAINTS); + + // invalid date time + assertParseFailure(parser, INVALID_DATETIME_DESC, + DateTime.MESSAGE_CONSTRAINTS); + + // invalid money + assertParseFailure(parser, INVALID_MONEY_DESC, + Money.MESSAGE_CONSTRAINTS_FORMAT); + + } + + @Test + public void parse_multipleRepeatedFields_failure() { + String userInput = NAME_DESC_AMY + DESCRIPTION_DESC_WATCH + + DATETIME_DESC_AMY_BAG + DATETIME_DESC_AMY_BAG; + + assertParseFailure(parser, userInput, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTransactionCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_allFieldsSpecified_success() { + String userInput = CUSTOMER_NAME_DESC_AMY + PRODUCT_DESCRIPTION_DESC_BAG + + DATETIME_DESC_AMY_BAG + MONEY_DESC_AMY_BAG; + + String[] customerKeywords = VALID_NAME_AMY.split("\\s+"); + CustomerContainsKeywordPredicate namePredicate = + new CustomerContainsKeywordPredicate(Arrays.asList(customerKeywords)); + + String[] productKeywords = VALID_DESCRIPTION_BAG.split("\\s+"); + ProductContainsKeywordPredicate descriptionPredicate = + new ProductContainsKeywordPredicate(Arrays.asList(productKeywords)); + + DateTimeEqualsPredicate dateTimePredicate = new DateTimeEqualsPredicate(new DateTime(VALID_DATETIME_AMY_BAG)); + MoneyEqualsPredicate moneyPredicate = new MoneyEqualsPredicate(new Money(VALID_MONEY_AMY_BAG)); + + JointTransactionPredicate jointPredicate = + new JointTransactionPredicate(Arrays.asList( + namePredicate, descriptionPredicate, dateTimePredicate, moneyPredicate + )); + + FindTransactionCommand expectedCommand = new FindTransactionCommand(jointPredicate); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_someFieldsSpecified_success() { + String userInput = DATETIME_DESC_AMY_BAG + MONEY_DESC_AMY_BAG; + + DateTimeEqualsPredicate dateTimePredicate = new DateTimeEqualsPredicate(new DateTime(VALID_DATETIME_AMY_BAG)); + MoneyEqualsPredicate moneyPredicate = new MoneyEqualsPredicate(new Money(VALID_MONEY_AMY_BAG)); + + JointTransactionPredicate jointPredicate = + new JointTransactionPredicate(Arrays.asList( + dateTimePredicate, moneyPredicate + )); + + FindTransactionCommand expectedCommand = new FindTransactionCommand(jointPredicate); + + assertParseSuccess(parser, userInput, expectedCommand); + + } +} + diff --git a/src/test/java/seedu/address/logic/parser/transaction/UndoTransactionCommandParserTest.java b/src/test/java/seedu/address/logic/parser/transaction/UndoTransactionCommandParserTest.java new file mode 100644 index 00000000000..9202affb419 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/transaction/UndoTransactionCommandParserTest.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser.transaction; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.transaction.UndoTransactionCommand; + + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the DeleteTransactionCommand code. For example, inputs "1" and "1 abc" take the + * same path through the DeleteTransactionCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class UndoTransactionCommandParserTest { + + private UndoTransactionCommandParser parser = new UndoTransactionCommandParser(); + + @Test + public void parse_validArgs_returnsUndoCommand() { + Index targetIndex = Index.fromOneBased(1); + assertParseSuccess(parser, "1", new UndoTransactionCommand(targetIndex)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UndoTransactionCommand.MESSAGE_USAGE)); + } +} + diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java deleted file mode 100644 index 87782528ecd..00000000000 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package seedu.address.model; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.testutil.PersonBuilder; - -public class AddressBookTest { - - private final AddressBook addressBook = new AddressBook(); - - @Test - public void constructor() { - assertEquals(Collections.emptyList(), addressBook.getPersonList()); - } - - @Test - public void resetData_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> addressBook.resetData(null)); - } - - @Test - public void resetData_withValidReadOnlyAddressBook_replacesData() { - AddressBook newData = getTypicalAddressBook(); - addressBook.resetData(newData); - assertEquals(newData, addressBook); - } - - @Test - public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { - // Two persons with the same identity fields - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - List newPersons = Arrays.asList(ALICE, editedAlice); - AddressBookStub newData = new AddressBookStub(newPersons); - - assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData)); - } - - @Test - public void hasPerson_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> addressBook.hasPerson(null)); - } - - @Test - public void hasPerson_personNotInAddressBook_returnsFalse() { - assertFalse(addressBook.hasPerson(ALICE)); - } - - @Test - public void hasPerson_personInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - assertTrue(addressBook.hasPerson(ALICE)); - } - - @Test - public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - assertTrue(addressBook.hasPerson(editedAlice)); - } - - @Test - public void getPersonList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () -> addressBook.getPersonList().remove(0)); - } - - /** - * A stub ReadOnlyAddressBook whose persons list can violate interface constraints. - */ - private static class AddressBookStub implements ReadOnlyAddressBook { - private final ObservableList persons = FXCollections.observableArrayList(); - - AddressBookStub(Collection persons) { - this.persons.setAll(persons); - } - - @Override - public ObservableList getPersonList() { - return persons; - } - } - -} diff --git a/src/test/java/seedu/address/model/InventorySystemTest.java b/src/test/java/seedu/address/model/InventorySystemTest.java new file mode 100644 index 00000000000..96969955696 --- /dev/null +++ b/src/test/java/seedu/address/model/InventorySystemTest.java @@ -0,0 +1,118 @@ +package seedu.address.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE; +import static seedu.address.testutil.customer.TypicalCustomers.getTypicalInventorySystem; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.logic.commands.customer.ClearCustomerCommand; +import seedu.address.logic.commands.product.ClearProductCommand; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.exceptions.DuplicatePersonException; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.Transaction; +import seedu.address.testutil.customer.CustomerBuilder; + +public class InventorySystemTest { + + private final InventorySystem addressBook = new InventorySystem(); + + @Test + public void constructor() { + assertEquals(Collections.emptyList(), addressBook.getPersonList()); + } + + @Test + public void resetData_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.resetData(null, null)); + } + + @Test + public void resetData_withValidReadOnlyAddressBook_replacesData() { + InventorySystem newData = getTypicalInventorySystem(); + addressBook.resetData(newData, ClearCustomerCommand.COMMAND_WORD); + addressBook.resetData(newData, ClearProductCommand.COMMAND_WORD); + assertEquals(newData, addressBook); + } + + @Test + public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { + // Two customers with the same identity fields + Customer editedAlice = new CustomerBuilder(ALICE).withTags(VALID_TAG_HUSBAND) + .build(); + List newCustomers = Arrays.asList(ALICE, editedAlice); + AddressBookStub newData = new AddressBookStub(newCustomers); + + assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData, + ClearCustomerCommand.COMMAND_WORD)); + } + + @Test + public void hasPerson_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> addressBook.hasPerson(null)); + } + + @Test + public void hasPerson_personNotInAddressBook_returnsFalse() { + assertFalse(addressBook.hasPerson(ALICE)); + } + + @Test + public void hasPerson_personInAddressBook_returnsTrue() { + addressBook.addPerson(ALICE); + assertTrue(addressBook.hasPerson(ALICE)); + } + + @Test + public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() { + addressBook.addPerson(ALICE); + Customer editedAlice = new CustomerBuilder(ALICE).withTags(VALID_TAG_HUSBAND) + .build(); + assertTrue(addressBook.hasPerson(editedAlice)); + } + + @Test + public void getPersonList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> addressBook.getPersonList().remove(0)); + } + + /** + * A stub ReadOnlyInventorySystem whose customers list can violate interface constraints. + */ + private static class AddressBookStub implements ReadOnlyInventorySystem { + private final ObservableList customers = FXCollections.observableArrayList(); + private final ObservableList products = FXCollections.observableArrayList(); + + AddressBookStub(Collection customers) { + this.customers.setAll(customers); + } + + @Override + public ObservableList getPersonList() { + return customers; + } + + @Override + public ObservableList getProductList() { + return products; + } + + @Override + public ObservableList getTransactionList() { + return null; + } + } + +} diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 2cf1418d116..433cc9cdef1 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -5,8 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE; +import static seedu.address.testutil.customer.TypicalCustomers.BENSON; import java.nio.file.Path; import java.nio.file.Paths; @@ -15,8 +15,8 @@ import org.junit.jupiter.api.Test; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.testutil.AddressBookBuilder; +import seedu.address.model.customer.NameContainsKeywordsPredicate; +import seedu.address.testutil.InventorySystemBuilder; public class ModelManagerTest { @@ -26,7 +26,7 @@ public class ModelManagerTest { public void constructor() { assertEquals(new UserPrefs(), modelManager.getUserPrefs()); assertEquals(new GuiSettings(), modelManager.getGuiSettings()); - assertEquals(new AddressBook(), new AddressBook(modelManager.getAddressBook())); + assertEquals(new InventorySystem(), new InventorySystem(modelManager.getInventorySystem())); } @Test @@ -62,14 +62,14 @@ public void setGuiSettings_validGuiSettings_setsGuiSettings() { @Test public void setAddressBookFilePath_nullPath_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> modelManager.setAddressBookFilePath(null)); + assertThrows(NullPointerException.class, () -> modelManager.setInventorySystemFilePath(null)); } @Test public void setAddressBookFilePath_validPath_setsAddressBookFilePath() { Path path = Paths.get("address/book/file/path"); - modelManager.setAddressBookFilePath(path); - assertEquals(path, modelManager.getAddressBookFilePath()); + modelManager.setInventorySystemFilePath(path); + assertEquals(path, modelManager.getInventorySystemFilePath()); } @Test @@ -90,13 +90,13 @@ public void hasPerson_personInAddressBook_returnsTrue() { @Test public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0)); + assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredCustomerList().remove(0)); } @Test public void equals() { - AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); - AddressBook differentAddressBook = new AddressBook(); + InventorySystem addressBook = new InventorySystemBuilder().withPerson(ALICE).withPerson(BENSON).build(); + InventorySystem differentAddressBook = new InventorySystem(); UserPrefs userPrefs = new UserPrefs(); // same values -> returns true @@ -118,11 +118,11 @@ public void equals() { // different filteredList -> returns false String[] keywords = ALICE.getName().fullName.split("\\s+"); - modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); + modelManager.updateFilteredCustomerList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); // resets modelManager to initial state for upcoming tests - modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + modelManager.updateFilteredCustomerList(PREDICATE_SHOW_ALL_PERSONS); // different userPrefs -> returns false UserPrefs differentUserPrefs = new UserPrefs(); diff --git a/src/test/java/seedu/address/model/person/AddressTest.java b/src/test/java/seedu/address/model/customer/AddressTest.java similarity index 87% rename from src/test/java/seedu/address/model/person/AddressTest.java rename to src/test/java/seedu/address/model/customer/AddressTest.java index dcd3be87b3a..e03843e1984 100644 --- a/src/test/java/seedu/address/model/person/AddressTest.java +++ b/src/test/java/seedu/address/model/customer/AddressTest.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -27,10 +27,10 @@ public void isValidAddress() { // invalid addresses assertFalse(Address.isValidAddress("")); // empty string assertFalse(Address.isValidAddress(" ")); // spaces only + assertFalse(Address.isValidAddress("Leng Inc; 1234 Market St; San Francisco CA 2349879; USA")); // long address // valid addresses assertTrue(Address.isValidAddress("Blk 456, Den Road, #01-355")); assertTrue(Address.isValidAddress("-")); // one character - assertTrue(Address.isValidAddress("Leng Inc; 1234 Market St; San Francisco CA 2349879; USA")); // long address } } diff --git a/src/test/java/seedu/address/model/customer/CustomerTest.java b/src/test/java/seedu/address/model/customer/CustomerTest.java new file mode 100644 index 00000000000..70bdd3d8d3d --- /dev/null +++ b/src/test/java/seedu/address/model/customer/CustomerTest.java @@ -0,0 +1,96 @@ +package seedu.address.model.customer; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE_ID; +import static seedu.address.testutil.customer.TypicalCustomers.BOB; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.customer.CustomerBuilder; + +public class CustomerTest { + + @Test + public void asObservableList_modifyList_throwsUnsupportedOperationException() { + Customer customer = new CustomerBuilder(ALICE_ID).build(); + assertThrows(UnsupportedOperationException.class, () -> customer.getTags().remove(0)); + } + + @Test + public void isSamePerson() { + // same object -> returns true + assertTrue(ALICE.isSamePerson(ALICE)); + + // null -> returns false + assertFalse(ALICE.isSamePerson(null)); + + // different phone and email, same id -> returns true + Customer editedAlice = new CustomerBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).build(); + assertTrue(ALICE.isSamePerson(editedAlice)); + + // different name, same id -> returns true + editedAlice = new CustomerBuilder(ALICE).withName(VALID_NAME_BOB).build(); + assertTrue(ALICE.isSamePerson(editedAlice)); + + // same name, same phone, different attributes -> returns false + editedAlice = new CustomerBuilder(ALICE).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) + .withTags(VALID_TAG_HUSBAND).build(); + assertFalse(ALICE.isSamePerson(editedAlice)); + + // same name, same email, different attributes -> returns false + editedAlice = new CustomerBuilder(ALICE).withPhone(VALID_PHONE_BOB).withAddress(VALID_ADDRESS_BOB) + .withTags(VALID_TAG_HUSBAND).build(); + assertFalse(ALICE.isSamePerson(editedAlice)); + + // same name, same phone, same email, different attributes -> returns false + editedAlice = new CustomerBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build(); + assertFalse(ALICE.isSamePerson(editedAlice)); + } + + @Test + public void equals() { + // same values -> returns true + Customer aliceCopy = new CustomerBuilder(ALICE).build(); + assertTrue(ALICE.equals(aliceCopy)); + + // same object -> returns true + assertTrue(ALICE.equals(ALICE)); + + // null -> returns false + assertFalse(ALICE.equals(null)); + + // different type -> returns false + assertFalse(ALICE.equals(5)); + + // different customer -> returns false + assertFalse(ALICE.equals(BOB)); + + // different name, same id -> returns true + Customer editedAlice = new CustomerBuilder(ALICE).withName(VALID_NAME_BOB).build(); + assertTrue(ALICE.equals(editedAlice)); + + // different phone, same id -> returns true + editedAlice = new CustomerBuilder(ALICE).withPhone(VALID_PHONE_BOB).build(); + assertTrue(ALICE.equals(editedAlice)); + + // different email, same id -> returns true + editedAlice = new CustomerBuilder(ALICE).withEmail(VALID_EMAIL_BOB).build(); + assertTrue(ALICE.equals(editedAlice)); + + // different address -> returns true + editedAlice = new CustomerBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build(); + assertTrue(ALICE.equals(editedAlice)); + + // different tags -> returns true + editedAlice = new CustomerBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build(); + assertTrue(ALICE.equals(editedAlice)); + } +} diff --git a/src/test/java/seedu/address/model/person/EmailTest.java b/src/test/java/seedu/address/model/customer/EmailTest.java similarity index 88% rename from src/test/java/seedu/address/model/person/EmailTest.java rename to src/test/java/seedu/address/model/customer/EmailTest.java index 7fa726ceb18..33484e0de39 100644 --- a/src/test/java/seedu/address/model/person/EmailTest.java +++ b/src/test/java/seedu/address/model/customer/EmailTest.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -47,6 +47,9 @@ public void isValidEmail() { assertFalse(Email.isValidEmail("peterjack@example.com.")); // domain name ends with a period assertFalse(Email.isValidEmail("peterjack@-example.com")); // domain name starts with a hyphen assertFalse(Email.isValidEmail("peterjack@example.com-")); // domain name ends with a hyphen + assertFalse(Email.isValidEmail("peter_jack@very-very-very-long-example.com")); // long domain name + assertFalse(Email.isValidEmail("if.you.dream.it_you.can.do.it@example.com")); // long local part + assertFalse(Email.isValidEmail("aaaaaaaaabbbbbbbbbbcccccccccccddddddddeeeeeeeeeee@example.com")); // long email // valid email assertTrue(Email.isValidEmail("PeterJack_1190@example.com")); @@ -55,7 +58,5 @@ public void isValidEmail() { assertTrue(Email.isValidEmail("!#$%&'*+/=?`{|}~^.-@example.org")); // special characters local part assertTrue(Email.isValidEmail("123@145")); // numeric local part and domain name assertTrue(Email.isValidEmail("a1+be!@example1.com")); // mixture of alphanumeric and special characters - assertTrue(Email.isValidEmail("peter_jack@very-very-very-long-example.com")); // long domain name - assertTrue(Email.isValidEmail("if.you.dream.it_you.can.do.it@example.com")); // long local part } } diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/customer/NameContainsKeywordsPredicateTest.java similarity index 73% rename from src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java rename to src/test/java/seedu/address/model/customer/NameContainsKeywordsPredicateTest.java index f136664e017..2fdb21aca27 100644 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ b/src/test/java/seedu/address/model/customer/NameContainsKeywordsPredicateTest.java @@ -1,7 +1,8 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE_ID; import java.util.Arrays; import java.util.Collections; @@ -9,7 +10,7 @@ import org.junit.jupiter.api.Test; -import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.customer.CustomerBuilder; public class NameContainsKeywordsPredicateTest { @@ -34,7 +35,7 @@ public void equals() { // null -> returns false assertFalse(firstPredicate.equals(null)); - // different person -> returns false + // different customer -> returns false assertFalse(firstPredicate.equals(secondPredicate)); } @@ -42,34 +43,34 @@ public void equals() { public void test_nameContainsKeywords_returnsTrue() { // One keyword NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.singletonList("Alice")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + assertTrue(predicate.test(new CustomerBuilder(ALICE_ID).withName("Alice Bob").build())); // Multiple keywords predicate = new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + assertTrue(predicate.test(new CustomerBuilder(ALICE_ID).withName("Alice Bob").build())); // Only one matching keyword predicate = new NameContainsKeywordsPredicate(Arrays.asList("Bob", "Carol")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build())); + assertTrue(predicate.test(new CustomerBuilder(ALICE_ID).withName("Alice Carol").build())); // Mixed-case keywords predicate = new NameContainsKeywordsPredicate(Arrays.asList("aLIce", "bOB")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + assertTrue(predicate.test(new CustomerBuilder(ALICE_ID).withName("Alice Bob").build())); } @Test public void test_nameDoesNotContainKeywords_returnsFalse() { // Zero keywords NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.emptyList()); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); + assertFalse(predicate.test(new CustomerBuilder(ALICE_ID).withName("Alice").build())); // Non-matching keyword predicate = new NameContainsKeywordsPredicate(Arrays.asList("Carol")); - assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + assertFalse(predicate.test(new CustomerBuilder(ALICE_ID).withName("Alice Bob").build())); // Keywords match phone, email and address, but does not match name predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") + assertFalse(predicate.test(new CustomerBuilder(ALICE_ID).withName("Alice").withPhone("12345") .withEmail("alice@email.com").withAddress("Main Street").build())); } } diff --git a/src/test/java/seedu/address/model/person/NameTest.java b/src/test/java/seedu/address/model/customer/NameTest.java similarity index 97% rename from src/test/java/seedu/address/model/person/NameTest.java rename to src/test/java/seedu/address/model/customer/NameTest.java index c9801392874..475956aad64 100644 --- a/src/test/java/seedu/address/model/person/NameTest.java +++ b/src/test/java/seedu/address/model/customer/NameTest.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/seedu/address/model/person/PhoneTest.java b/src/test/java/seedu/address/model/customer/PhoneTest.java similarity index 97% rename from src/test/java/seedu/address/model/person/PhoneTest.java rename to src/test/java/seedu/address/model/customer/PhoneTest.java index 8dd52766a5f..b8a0fd523cc 100644 --- a/src/test/java/seedu/address/model/person/PhoneTest.java +++ b/src/test/java/seedu/address/model/customer/PhoneTest.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/seedu/address/model/customer/UniqueCustomerListTest.java b/src/test/java/seedu/address/model/customer/UniqueCustomerListTest.java new file mode 100644 index 00000000000..31985539be6 --- /dev/null +++ b/src/test/java/seedu/address/model/customer/UniqueCustomerListTest.java @@ -0,0 +1,170 @@ +package seedu.address.model.customer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE; +import static seedu.address.testutil.customer.TypicalCustomers.BOB; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.customer.exceptions.DuplicatePersonException; +import seedu.address.model.customer.exceptions.PersonNotFoundException; +import seedu.address.testutil.customer.CustomerBuilder; + +public class UniqueCustomerListTest { + + private final UniqueCustomerList uniqueCustomerList = new UniqueCustomerList(); + + @Test + public void contains_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueCustomerList.contains(null)); + } + + @Test + public void contains_personNotInList_returnsFalse() { + assertFalse(uniqueCustomerList.contains(ALICE)); + } + + @Test + public void contains_personInList_returnsTrue() { + uniqueCustomerList.add(ALICE); + assertTrue(uniqueCustomerList.contains(ALICE)); + } + + @Test + public void contains_personWithSameIdentityFieldsInListExceptAddress_returnsFalse() { + uniqueCustomerList.add(ALICE); + Customer editedAlice = new CustomerBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + .build(); + assertFalse(uniqueCustomerList.contains(editedAlice)); + } + + @Test + public void add_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueCustomerList.add(null)); + } + + @Test + public void add_duplicatePerson_throwsDuplicatePersonException() { + uniqueCustomerList.add(ALICE); + assertThrows(DuplicatePersonException.class, () -> uniqueCustomerList.add(ALICE)); + } + + @Test + public void setPerson_nullTargetPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueCustomerList.setPerson(null, ALICE)); + } + + @Test + public void setPerson_nullEditedPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueCustomerList.setPerson(ALICE, null)); + } + + @Test + public void setPerson_targetPersonNotInList_throwsPersonNotFoundException() { + assertThrows(PersonNotFoundException.class, () -> uniqueCustomerList.setPerson(ALICE, ALICE)); + } + + @Test + public void setPerson_editedPersonIsSamePerson_success() { + uniqueCustomerList.add(ALICE); + uniqueCustomerList.setPerson(ALICE, ALICE); + UniqueCustomerList expectedUniqueCustomerList = new UniqueCustomerList(); + expectedUniqueCustomerList.add(ALICE); + assertEquals(expectedUniqueCustomerList, uniqueCustomerList); + } + + @Test + public void setPerson_editedPersonHasSameIdentity_success() { + uniqueCustomerList.add(ALICE); + Customer editedAlice = new CustomerBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + .build(); + uniqueCustomerList.setPerson(ALICE, editedAlice); + UniqueCustomerList expectedUniqueCustomerList = new UniqueCustomerList(); + expectedUniqueCustomerList.add(editedAlice); + assertEquals(expectedUniqueCustomerList, uniqueCustomerList); + } + + @Test + public void setPerson_editedPersonHasDifferentIdentity_success() { + uniqueCustomerList.add(ALICE); + uniqueCustomerList.setPerson(ALICE, BOB); + UniqueCustomerList expectedUniqueCustomerList = new UniqueCustomerList(); + expectedUniqueCustomerList.add(BOB); + assertEquals(expectedUniqueCustomerList, uniqueCustomerList); + } + + @Test + public void setPerson_editedPersonHasNonUniqueIdentity_throwsDuplicatePersonException() { + uniqueCustomerList.add(ALICE); + uniqueCustomerList.add(BOB); + assertThrows(DuplicatePersonException.class, () -> uniqueCustomerList.setPerson(ALICE, BOB)); + } + + @Test + public void remove_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueCustomerList.remove(null)); + } + + @Test + public void remove_personDoesNotExist_throwsPersonNotFoundException() { + assertThrows(PersonNotFoundException.class, () -> uniqueCustomerList.remove(ALICE)); + } + + @Test + public void remove_existingPerson_removesPerson() { + uniqueCustomerList.add(ALICE); + uniqueCustomerList.remove(ALICE); + UniqueCustomerList expectedUniqueCustomerList = new UniqueCustomerList(); + assertEquals(expectedUniqueCustomerList, uniqueCustomerList); + } + + @Test + public void setPersons_nullUniquePersonList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueCustomerList.setPersons((UniqueCustomerList) null)); + } + + @Test + public void setPersons_uniquePersonList_replacesOwnListWithProvidedUniquePersonList() { + uniqueCustomerList.add(ALICE); + UniqueCustomerList expectedUniqueCustomerList = new UniqueCustomerList(); + expectedUniqueCustomerList.add(BOB); + uniqueCustomerList.setPersons(expectedUniqueCustomerList); + assertEquals(expectedUniqueCustomerList, uniqueCustomerList); + } + + @Test + public void setPersons_nullList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueCustomerList.setPersons((List) null)); + } + + @Test + public void setPersons_list_replacesOwnListWithProvidedList() { + uniqueCustomerList.add(ALICE); + List customerList = Collections.singletonList(BOB); + uniqueCustomerList.setPersons(customerList); + UniqueCustomerList expectedUniqueCustomerList = new UniqueCustomerList(); + expectedUniqueCustomerList.add(BOB); + assertEquals(expectedUniqueCustomerList, uniqueCustomerList); + } + + @Test + public void setPersons_listWithDuplicatePersons_throwsDuplicatePersonException() { + List listWithDuplicateCustomers = Arrays.asList(ALICE, ALICE); + assertThrows(DuplicatePersonException.class, () -> uniqueCustomerList.setPersons(listWithDuplicateCustomers)); + } + + @Test + public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () + -> uniqueCustomerList.asUnmodifiableObservableList().remove(0)); + } +} diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java deleted file mode 100644 index 7c1058d8635..00000000000 --- a/src/test/java/seedu/address/model/person/PersonTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BOB; - -import org.junit.jupiter.api.Test; - -import seedu.address.testutil.PersonBuilder; - -public class PersonTest { - - @Test - public void asObservableList_modifyList_throwsUnsupportedOperationException() { - Person person = new PersonBuilder().build(); - assertThrows(UnsupportedOperationException.class, () -> person.getTags().remove(0)); - } - - @Test - public void isSamePerson() { - // same object -> returns true - assertTrue(ALICE.isSamePerson(ALICE)); - - // null -> returns false - assertFalse(ALICE.isSamePerson(null)); - - // different phone and email -> returns false - Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).build(); - assertFalse(ALICE.isSamePerson(editedAlice)); - - // different name -> returns false - editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); - assertFalse(ALICE.isSamePerson(editedAlice)); - - // same name, same phone, different attributes -> returns true - editedAlice = new PersonBuilder(ALICE).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) - .withTags(VALID_TAG_HUSBAND).build(); - assertTrue(ALICE.isSamePerson(editedAlice)); - - // same name, same email, different attributes -> returns true - editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withAddress(VALID_ADDRESS_BOB) - .withTags(VALID_TAG_HUSBAND).build(); - assertTrue(ALICE.isSamePerson(editedAlice)); - - // same name, same phone, same email, different attributes -> returns true - editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build(); - assertTrue(ALICE.isSamePerson(editedAlice)); - } - - @Test - public void equals() { - // same values -> returns true - Person aliceCopy = new PersonBuilder(ALICE).build(); - assertTrue(ALICE.equals(aliceCopy)); - - // same object -> returns true - assertTrue(ALICE.equals(ALICE)); - - // null -> returns false - assertFalse(ALICE.equals(null)); - - // different type -> returns false - assertFalse(ALICE.equals(5)); - - // different person -> returns false - assertFalse(ALICE.equals(BOB)); - - // different name -> returns false - Person editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); - assertFalse(ALICE.equals(editedAlice)); - - // different phone -> returns false - editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).build(); - assertFalse(ALICE.equals(editedAlice)); - - // different email -> returns false - editedAlice = new PersonBuilder(ALICE).withEmail(VALID_EMAIL_BOB).build(); - assertFalse(ALICE.equals(editedAlice)); - - // different address -> returns false - editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build(); - assertFalse(ALICE.equals(editedAlice)); - - // different tags -> returns false - editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build(); - assertFalse(ALICE.equals(editedAlice)); - } -} diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java deleted file mode 100644 index 1cc5fe9e0fe..00000000000 --- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java +++ /dev/null @@ -1,170 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BOB; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; -import seedu.address.testutil.PersonBuilder; - -public class UniquePersonListTest { - - private final UniquePersonList uniquePersonList = new UniquePersonList(); - - @Test - public void contains_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.contains(null)); - } - - @Test - public void contains_personNotInList_returnsFalse() { - assertFalse(uniquePersonList.contains(ALICE)); - } - - @Test - public void contains_personInList_returnsTrue() { - uniquePersonList.add(ALICE); - assertTrue(uniquePersonList.contains(ALICE)); - } - - @Test - public void contains_personWithSameIdentityFieldsInList_returnsTrue() { - uniquePersonList.add(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - assertTrue(uniquePersonList.contains(editedAlice)); - } - - @Test - public void add_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.add(null)); - } - - @Test - public void add_duplicatePerson_throwsDuplicatePersonException() { - uniquePersonList.add(ALICE); - assertThrows(DuplicatePersonException.class, () -> uniquePersonList.add(ALICE)); - } - - @Test - public void setPerson_nullTargetPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.setPerson(null, ALICE)); - } - - @Test - public void setPerson_nullEditedPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.setPerson(ALICE, null)); - } - - @Test - public void setPerson_targetPersonNotInList_throwsPersonNotFoundException() { - assertThrows(PersonNotFoundException.class, () -> uniquePersonList.setPerson(ALICE, ALICE)); - } - - @Test - public void setPerson_editedPersonIsSamePerson_success() { - uniquePersonList.add(ALICE); - uniquePersonList.setPerson(ALICE, ALICE); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(ALICE); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPerson_editedPersonHasSameIdentity_success() { - uniquePersonList.add(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - uniquePersonList.setPerson(ALICE, editedAlice); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(editedAlice); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPerson_editedPersonHasDifferentIdentity_success() { - uniquePersonList.add(ALICE); - uniquePersonList.setPerson(ALICE, BOB); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(BOB); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPerson_editedPersonHasNonUniqueIdentity_throwsDuplicatePersonException() { - uniquePersonList.add(ALICE); - uniquePersonList.add(BOB); - assertThrows(DuplicatePersonException.class, () -> uniquePersonList.setPerson(ALICE, BOB)); - } - - @Test - public void remove_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.remove(null)); - } - - @Test - public void remove_personDoesNotExist_throwsPersonNotFoundException() { - assertThrows(PersonNotFoundException.class, () -> uniquePersonList.remove(ALICE)); - } - - @Test - public void remove_existingPerson_removesPerson() { - uniquePersonList.add(ALICE); - uniquePersonList.remove(ALICE); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPersons_nullUniquePersonList_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.setPersons((UniquePersonList) null)); - } - - @Test - public void setPersons_uniquePersonList_replacesOwnListWithProvidedUniquePersonList() { - uniquePersonList.add(ALICE); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(BOB); - uniquePersonList.setPersons(expectedUniquePersonList); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPersons_nullList_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> uniquePersonList.setPersons((List) null)); - } - - @Test - public void setPersons_list_replacesOwnListWithProvidedList() { - uniquePersonList.add(ALICE); - List personList = Collections.singletonList(BOB); - uniquePersonList.setPersons(personList); - UniquePersonList expectedUniquePersonList = new UniquePersonList(); - expectedUniquePersonList.add(BOB); - assertEquals(expectedUniquePersonList, uniquePersonList); - } - - @Test - public void setPersons_listWithDuplicatePersons_throwsDuplicatePersonException() { - List listWithDuplicatePersons = Arrays.asList(ALICE, ALICE); - assertThrows(DuplicatePersonException.class, () -> uniquePersonList.setPersons(listWithDuplicatePersons)); - } - - @Test - public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () - -> uniquePersonList.asUnmodifiableObservableList().remove(0)); - } -} diff --git a/src/test/java/seedu/address/model/product/DescriptionContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/product/DescriptionContainsKeywordsPredicateTest.java new file mode 100644 index 00000000000..3b88b486450 --- /dev/null +++ b/src/test/java/seedu/address/model/product/DescriptionContainsKeywordsPredicateTest.java @@ -0,0 +1,81 @@ +package seedu.address.model.product; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.product.TypicalProducts.ABACUS_ID; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.product.ProductBuilder; + +public class DescriptionContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + DescriptionContainsKeywordsPredicate firstPredicate = new + DescriptionContainsKeywordsPredicate(firstPredicateKeywordList); + DescriptionContainsKeywordsPredicate secondPredicate = new + DescriptionContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + DescriptionContainsKeywordsPredicate firstPredicateCopy = new + DescriptionContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different keywords -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_descriptionContainsKeywords_returnsTrue() { + // One keyword + DescriptionContainsKeywordsPredicate predicate = new + DescriptionContainsKeywordsPredicate(Collections.singletonList("Abacus")); + assertTrue(predicate.test(new ProductBuilder(ABACUS_ID).withDescription("Abacus Bob").build())); + + // Multiple keywords + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("Abacus", "Bob")); + assertTrue(predicate.test(new ProductBuilder(ABACUS_ID).withDescription("Abacus Bob").build())); + + // Only one matching keyword + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("Bob", "Carol")); + assertTrue(predicate.test(new ProductBuilder(ABACUS_ID).withDescription("Abacus Carol").build())); + + // Mixed-case keywords + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("aBacus", "bOB")); + assertTrue(predicate.test(new ProductBuilder(ABACUS_ID).withDescription("Abacus Bob").build())); + } + + @Test + public void test_descriptionDoesNotContainKeywords_returnsFalse() { + // Zero keywords + DescriptionContainsKeywordsPredicate predicate = new + DescriptionContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new ProductBuilder(ABACUS_ID).withDescription("Abacus").build())); + + // Non-matching keyword + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("Carol")); + assertFalse(predicate.test(new ProductBuilder(ABACUS_ID).withDescription("Abacus Bob").build())); + + // Keywords match phone, email and address, but does not match name + predicate = new DescriptionContainsKeywordsPredicate(Arrays.asList("12345", "10", "Main", "Street")); + assertFalse(predicate.test(new ProductBuilder(ABACUS_ID).withDescription("Abacus").withPrice("12345") + .withQuantity("10").withMoney("10").build())); + } +} diff --git a/src/test/java/seedu/address/model/product/DescriptionTest.java b/src/test/java/seedu/address/model/product/DescriptionTest.java new file mode 100644 index 00000000000..797c93f8875 --- /dev/null +++ b/src/test/java/seedu/address/model/product/DescriptionTest.java @@ -0,0 +1,38 @@ +package seedu.address.model.product; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.util.Description; + +public class DescriptionTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Description(null)); + } + + @Test + public void constructor_invalidDescription_throwsIllegalArgumentException() { + String invalidDescription = ""; + assertThrows(IllegalArgumentException.class, () -> new Description(invalidDescription)); + } + + @Test + public void isValidDescription() { + // null address + assertThrows(NullPointerException.class, () -> Description.isValidDescription(null)); + + // invalid addresses + assertFalse(Description.isValidDescription("")); // empty string + assertFalse(Description.isValidDescription(" ")); // spaces only + + // valid addresses + assertTrue(Description.isValidDescription("Black watch")); + assertTrue(Description.isValidDescription("-")); // one character + assertTrue(Description.isValidDescription("Very long description of black watch")); // long address + } +} diff --git a/src/test/java/seedu/address/model/product/PriceTest.java b/src/test/java/seedu/address/model/product/PriceTest.java new file mode 100644 index 00000000000..4f8bec000fd --- /dev/null +++ b/src/test/java/seedu/address/model/product/PriceTest.java @@ -0,0 +1,39 @@ +package seedu.address.model.product; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class PriceTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Price(null)); + } + + @Test + public void constructor_invalidPrice_throwsIllegalArgumentException() { + String invalidPrice = ""; + assertThrows(IllegalArgumentException.class, () -> new Price(invalidPrice)); + } + + @Test + public void isValidPrice() { + // null prices + assertThrows(NullPointerException.class, () -> Price.isValidPrice(null)); + + // invalid prices + assertFalse(Price.isValidPrice("")); // empty string + assertFalse(Price.isValidPrice(" ")); // spaces only + assertFalse(Price.isValidPrice("price")); // non-numeric + assertFalse(Price.isValidPrice("9011p041")); // alphabets within digits + assertFalse(Price.isValidPrice("9312 1534")); // spaces within digits + assertFalse(Price.isValidPrice("124293842033123")); // long prices + + // valid prices + assertTrue(Price.isValidPrice("911")); // exactly 3 numbers + assertTrue(Price.isValidPrice("1000000")); + } +} diff --git a/src/test/java/seedu/address/model/product/ProductQuantityTest.java b/src/test/java/seedu/address/model/product/ProductQuantityTest.java new file mode 100644 index 00000000000..8af47c636f1 --- /dev/null +++ b/src/test/java/seedu/address/model/product/ProductQuantityTest.java @@ -0,0 +1,95 @@ +package seedu.address.model.product; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class ProductQuantityTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new ProductQuantity(null)); + } + + @Test + public void constructor_invalidQuantity_throwsIllegalArgumentException() { + String invalidQuantity = ""; + assertThrows(IllegalArgumentException.class, () -> new ProductQuantity(invalidQuantity)); + } + + @Test + public void isValidQuantity() { + // null quantity + assertThrows(NullPointerException.class, () -> ProductQuantity.isValidFormat(null)); + + // invalid quantities + assertFalse(ProductQuantity.isValidFormat("")); // empty string + assertFalse(ProductQuantity.isValidFormat(" ")); // spaces only + assertFalse(ProductQuantity.isValidFormat("price")); // non-numeric + assertFalse(ProductQuantity.isValidFormat("9011p041")); // alphabets within digits + assertFalse(ProductQuantity.isValidFormat("9312 1534")); // spaces within digits + assertFalse(ProductQuantity.isValidFormat("93121534")); // exceed max value + + // valid quantities + assertTrue(ProductQuantity.isValidFormat("911")); // exactly 3 numbers + assertTrue(ProductQuantity.isValidFormat("1000000")); // long prices + } + + @Test + public void equals() { + //same value -> returns true + ProductQuantity firstQuantity = new ProductQuantity(10); + ProductQuantity secondQuantity = new ProductQuantity(10); + assertTrue(firstQuantity.equals(secondQuantity)); + + //same object -> returns false + ProductQuantity quantity = new ProductQuantity(10); + assertTrue(quantity.equals(quantity)); + + //compared with null -> throws exception + assertThrows(NullPointerException.class, () -> quantity.compareTo(null)); + + //different value -> returns false + firstQuantity = new ProductQuantity(11); + secondQuantity = new ProductQuantity(1); + assertFalse(firstQuantity.equals(secondQuantity)); + } + + @Test + public void minus() { + //minus null -> throws null pointer exception + ProductQuantity five = new ProductQuantity(5); + assertThrows(NullPointerException.class, () -> five.plus(null)); + + //self minus self -> return 0 + ProductQuantity zero = new ProductQuantity(0); + assertTrue(five.minus(five).equals(zero)); + + //self minus number larger than self -> throws exception + ProductQuantity six = new ProductQuantity(6); + assertThrows(IllegalArgumentException.class, () -> five.minus(six)); + + //self minus number smaller than self -> return normal result + ProductQuantity one = new ProductQuantity(1); + assertTrue(six.minus(five).equals(one)); + } + + @Test + public void plus() { + //plus null -> throws exception + ProductQuantity five = new ProductQuantity(5); + assertThrows(NullPointerException.class, () -> five.plus(null)); + + //self minus self -> return self x 2 + ProductQuantity ten = new ProductQuantity(10); + assertTrue(five.plus(five).equals(ten)); + + //self plus other number -> returns quantity with value = self + other + ProductQuantity six = new ProductQuantity(6); + ProductQuantity eleven = new ProductQuantity(11); + assertTrue(five.plus(six).equals(eleven)); + } +} + diff --git a/src/test/java/seedu/address/model/transaction/CustomerContainsKeywordPredicateTest.java b/src/test/java/seedu/address/model/transaction/CustomerContainsKeywordPredicateTest.java new file mode 100644 index 00000000000..923d2514497 --- /dev/null +++ b/src/test/java/seedu/address/model/transaction/CustomerContainsKeywordPredicateTest.java @@ -0,0 +1,88 @@ +package seedu.address.model.transaction; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE; +import static seedu.address.testutil.transaction.TypicalTransactions.ALICE_BUY_ONE_BAG_MARCH_FIRST; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.customer.Customer; +import seedu.address.testutil.customer.CustomerBuilder; +import seedu.address.testutil.transaction.TransactionBuilder; + +public class CustomerContainsKeywordPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("Alice"); + List secondPredicateKeywordList = Arrays.asList("Alice", "Bob"); + + CustomerContainsKeywordPredicate firstPredicate = new + CustomerContainsKeywordPredicate(firstPredicateKeywordList); + CustomerContainsKeywordPredicate secondPredicate = new + CustomerContainsKeywordPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + CustomerContainsKeywordPredicate firstPredicateCopy = new + CustomerContainsKeywordPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different keywords -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_customerContainsKeyword_returnsTrue() { + //one keyword + CustomerContainsKeywordPredicate predicate = new + CustomerContainsKeywordPredicate(Collections.singletonList("Alice")); + assertTrue(predicate.test(ALICE_BUY_ONE_BAG_MARCH_FIRST)); + + //multiple keyword + predicate = new CustomerContainsKeywordPredicate(Arrays.asList("Alice", "Bob")); + Customer editedAlice = new CustomerBuilder(ALICE).withName("Alice Bob").build(); + assertTrue(predicate.test(new TransactionBuilder(ALICE_BUY_ONE_BAG_MARCH_FIRST) + .withCustomer(editedAlice).build())); + + //only one matching keyword + predicate = new CustomerContainsKeywordPredicate(Arrays.asList("Alice", "Andreas")); + assertTrue(predicate.test(new TransactionBuilder(ALICE_BUY_ONE_BAG_MARCH_FIRST) + .withCustomer(editedAlice).build())); + + //mixed case keywords + predicate = new CustomerContainsKeywordPredicate(Arrays.asList("aLicE", "bOb")); + assertTrue(predicate.test(new TransactionBuilder(ALICE_BUY_ONE_BAG_MARCH_FIRST) + .withCustomer(editedAlice).build())); + } + + @Test + public void test_customerContainsKeyword_returnsFalse() { + // no keyword + CustomerContainsKeywordPredicate predicate = + new CustomerContainsKeywordPredicate(Collections.emptyList()); + assertFalse(predicate.test(ALICE_BUY_ONE_BAG_MARCH_FIRST)); + + //no matching keyword + predicate = new CustomerContainsKeywordPredicate(Arrays.asList("random", "string")); + assertFalse(predicate.test(ALICE_BUY_ONE_BAG_MARCH_FIRST)); + + //keywords matching other attributes rather than customer name. + predicate = new CustomerContainsKeywordPredicate(Arrays.asList("1", "2020-03-01", "10:00", "20", "promotion")); + assertFalse(predicate.test(new TransactionBuilder(ALICE_BUY_ONE_BAG_MARCH_FIRST) + .withQuantity(1).withDateTime("2020-03-01 10:00").withMoney(20).withDescription("promotion").build())); + } +} diff --git a/src/test/java/seedu/address/model/transaction/DateTimeTest.java b/src/test/java/seedu/address/model/transaction/DateTimeTest.java new file mode 100644 index 00000000000..63611980d65 --- /dev/null +++ b/src/test/java/seedu/address/model/transaction/DateTimeTest.java @@ -0,0 +1,113 @@ +package seedu.address.model.transaction; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.transaction.TypicalDateTimes.MARCH_FIRST_2020_10AM; +import static seedu.address.testutil.transaction.TypicalDateTimes.MARCH_FIRST_2020_5PM; +import static seedu.address.testutil.transaction.TypicalDateTimes.MARCH_SECOND_2020_5PM; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +public class DateTimeTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new DateTime((String) null)); + } + + @Test + public void constructor_invalidDateTimeString_throwsArgumentException() { + //invalid format. + assertThrows(IllegalArgumentException.class, () -> new DateTime("")); + + //invalid month value. + assertThrows(IllegalArgumentException.class, () -> new DateTime("2020-13-02 10:00")); + assertThrows(IllegalArgumentException.class, () -> new DateTime("2020-00-02 10:00")); + assertThrows(IllegalArgumentException.class, () -> new DateTime("2020-20-02 10:00")); + + //invalid date value. + assertThrows(IllegalArgumentException.class, () -> new DateTime("2019-02-29 10:00")); + assertThrows(IllegalArgumentException.class, () -> new DateTime("2020-02-30 10:00")); + assertThrows(IllegalArgumentException.class, () -> new DateTime("2020-04-31 10:00")); + assertThrows(IllegalArgumentException.class, () -> new DateTime("2020-03-32 10:00")); + assertThrows(IllegalArgumentException.class, () -> new DateTime("2020-03-00 10:00")); + + //invalid time value. + assertThrows(IllegalArgumentException.class, () -> new DateTime("2020-02-20 12:60")); + assertThrows(IllegalArgumentException.class, () -> new DateTime("2020-02-20 12:61")); + assertThrows(IllegalArgumentException.class, () -> new DateTime("2020-02-20 25:00")); + assertThrows(IllegalArgumentException.class, () -> new DateTime("2020-02-20 30:00")); + } + + @Test + public void isValidDateTime() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm"); + LocalDateTime currentDateTime = LocalDateTime.now(ZoneId.of("Asia/Singapore")); + LocalDateTime currentPlusOneHour = currentDateTime.plusHours(1); + LocalDateTime currentPlusOneDay = currentDateTime.plusDays(1); + + + //null date time + assertThrows(NullPointerException.class, () -> DateTime.isValidDateTime(null)); + + //invalid date time string + assertFalse(DateTime.isValidDateTime("")); // empty string + assertFalse(DateTime.isValidDateTime(" ")); // space only + assertFalse(DateTime.isValidDateTime("201")); // invalid year format + assertFalse(DateTime.isValidDateTime("12345")); // invalid year format + assertFalse(DateTime.isValidDateTime("2012")); // year only + assertFalse(DateTime.isValidDateTime("2020-02-02")); // missing time value + assertFalse(DateTime.isValidDateTime("2020-02-0210:00")); // missing space + assertFalse(DateTime.isValidDateTime("2020-02-02 10")); // missing minute value + assertFalse(DateTime.isValidDateTime("02-02-2020 10:00")); // wrong date format + assertFalse(DateTime.isValidDateTime("2020/02/02 10:00")); // wrong date format + assertFalse(DateTime.isValidDateTime(currentPlusOneHour.format(formatter))); // in future by one hour + assertFalse(DateTime.isValidDateTime(currentPlusOneDay.format(formatter))); // in future by one day + + //valid date time string + assertTrue(DateTime.isValidDateTime("2020-02-20 10:00")); + assertTrue(DateTime.isValidDateTime("2020-02-28 20:00")); + assertTrue(DateTime.isValidDateTime("2020-03-15 23:59")); + } + + @Test + public void isBefore() { + //one's day before another's day -> returns true + assertTrue(MARCH_FIRST_2020_5PM.isBefore(MARCH_SECOND_2020_5PM)); + + //one's hour before another's -> returns true + assertTrue(MARCH_FIRST_2020_10AM.isBefore(MARCH_FIRST_2020_5PM)); + + //compared with null -> throw null pointer exception + assertThrows(NullPointerException.class, () -> MARCH_FIRST_2020_5PM.isBefore(null)); + } + + @Test + public void isAfter() { + //one's day after another's day -> return true + assertTrue(MARCH_SECOND_2020_5PM.isAfter(MARCH_FIRST_2020_5PM)); + + //one's hour after another's hour -> return true + assertTrue(MARCH_FIRST_2020_5PM.isAfter(MARCH_FIRST_2020_10AM)); + + //compared with null -> throw null pointer exception + assertThrows(NullPointerException.class, () -> MARCH_FIRST_2020_5PM.isAfter(null)); + } + + @Test + public void isOnSameDay() { + //two DateTime objects on the same day but different time -> returns true + assertTrue(MARCH_FIRST_2020_5PM.isOnSameDay(MARCH_FIRST_2020_10AM)); + + //two identical datetime objects -> returns true + assertTrue(MARCH_FIRST_2020_5PM.isOnSameDay(MARCH_FIRST_2020_5PM)); + + //tested with null -> throws null pointer exception + assertThrows(NullPointerException.class, () -> MARCH_FIRST_2020_5PM.isOnSameDay(null)); + } +} diff --git a/src/test/java/seedu/address/model/transaction/TransactionFactoryTest.java b/src/test/java/seedu/address/model/transaction/TransactionFactoryTest.java new file mode 100644 index 00000000000..48483434618 --- /dev/null +++ b/src/test/java/seedu/address/model/transaction/TransactionFactoryTest.java @@ -0,0 +1,66 @@ +package seedu.address.model.transaction; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.testutil.transaction.TransactionFactoryBuilder; + +public class TransactionFactoryTest { + + @Test + public void createTransaction() throws CommandException { + // null model -> throws null pointer exception + TransactionFactory defaultFactory = new TransactionFactoryBuilder().build(); + assertThrows(NullPointerException.class, () -> defaultFactory.createTransaction(null)); + } + + @Test + public void equals() { + // same value -> returns true + TransactionFactory defaultFactory = new TransactionFactoryBuilder().build(); + TransactionFactory defaultFactoryCopy = new TransactionFactoryBuilder(defaultFactory).build(); + assertTrue(defaultFactory.equals(defaultFactoryCopy)); + + // same object -> returns true + assertTrue(defaultFactory.equals(defaultFactory)); + + // null => returns false + assertFalse(defaultFactory.equals(null)); + + // different customer index -> returns false + TransactionFactory defaultFactoryWithCustomerTwo = new TransactionFactoryBuilder(defaultFactory) + .withCustomerIndex(Index.fromOneBased(2)).build(); + assertFalse(defaultFactory.equals(defaultFactoryWithCustomerTwo)); + + // different product index -> returns false + TransactionFactory defaultFactoryWithProductTwo = new TransactionFactoryBuilder(defaultFactory) + .withProductIndex(Index.fromOneBased(2)).build(); + assertFalse(defaultFactory.equals(defaultFactoryWithProductTwo)); + + // different dateTime -> returns false + TransactionFactory defaultFactoryWithDiffDateTime = new TransactionFactoryBuilder(defaultFactory) + .withDateTime("2020-03-03 10:00").build(); + assertFalse(defaultFactory.equals(defaultFactoryWithDiffDateTime)); + + // different quantity -> returns false + TransactionFactory defaultFactoryWithDiffQuantity = new TransactionFactoryBuilder(defaultFactory) + .withQuantity(2).build(); + assertFalse(defaultFactory.equals(defaultFactoryWithDiffQuantity)); + + // different money -> returns false + TransactionFactory defaultFactoryWithDiffMoney = new TransactionFactoryBuilder(defaultFactory) + .withMoney(2).build(); + assertFalse(defaultFactory.equals(defaultFactoryWithDiffMoney)); + + // different description -> returns false + TransactionFactory defaultFactoryWithDiffDescription = new TransactionFactoryBuilder(defaultFactory) + .withDescription("normal price").build(); + assertFalse(defaultFactory.equals(defaultFactoryWithDiffDescription)); + + } +} diff --git a/src/test/java/seedu/address/model/transaction/TransactionQuantityTest.java b/src/test/java/seedu/address/model/transaction/TransactionQuantityTest.java new file mode 100644 index 00000000000..42490858dbf --- /dev/null +++ b/src/test/java/seedu/address/model/transaction/TransactionQuantityTest.java @@ -0,0 +1,107 @@ +package seedu.address.model.transaction; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class TransactionQuantityTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new TransactionQuantity(null)); + } + + @Test + public void constructor_invalidQuantity_throwsIllegalArgumentException() { + String invalidQuantity = ""; + assertThrows(IllegalArgumentException.class, () -> new TransactionQuantity(invalidQuantity)); + } + + @Test + public void isValidFormat() { + // null quantity + assertThrows(NullPointerException.class, () -> TransactionQuantity.isValidFormat(null)); + + // invalid quantities + assertFalse(TransactionQuantity.isValidFormat("")); // empty string + assertFalse(TransactionQuantity.isValidFormat(" ")); // spaces only + assertFalse(TransactionQuantity.isValidFormat("0")); // smaller than minimum value + assertFalse(TransactionQuantity.isValidFormat("price")); // non-numeric + assertFalse(TransactionQuantity.isValidFormat("9011p041")); // alphabets within digits + assertFalse(TransactionQuantity.isValidFormat("9312 1534")); // spaces within digits + assertFalse(TransactionQuantity.isValidFormat("93121534")); // exceed max value + + // valid quantities + assertTrue(TransactionQuantity.isValidFormat("1")); // minimal valid number + assertTrue(TransactionQuantity.isValidFormat("911")); // exactly 3 numbers + assertTrue(TransactionQuantity.isValidFormat("1000000")); // long prices + } + + @Test + public void isValidValue() { + // invalid values + assertFalse(TransactionQuantity.isValidValue(0)); // smaller than minimal + assertFalse(TransactionQuantity.isValidValue(1000001)); // exceed max value + assertFalse(TransactionQuantity.isValidValue(2000001)); // exceed max value + + // valid values + assertTrue(TransactionQuantity.isValidValue(1)); // minimal valid number + assertTrue(TransactionQuantity.isValidValue(999999)); // maximal valid number + assertTrue(TransactionQuantity.isValidValue(10)); // two digits + assertTrue(TransactionQuantity.isValidValue(143)); // three digits + } + + @Test + public void equals() { + //same value -> returns true + TransactionQuantity firstQuantity = new TransactionQuantity(10); + TransactionQuantity secondQuantity = new TransactionQuantity(10); + assertTrue(firstQuantity.equals(secondQuantity)); + + //same object -> returns false + TransactionQuantity quantity = new TransactionQuantity(10); + assertTrue(quantity.equals(quantity)); + + //compared with null -> throws exception + assertThrows(NullPointerException.class, () -> quantity.compareTo(null)); + + //different value -> returns false + firstQuantity = new TransactionQuantity(11); + secondQuantity = new TransactionQuantity(1); + assertFalse(firstQuantity.equals(secondQuantity)); + } + + @Test + public void minus() { + //minus null -> throws null pointer exception + TransactionQuantity five = new TransactionQuantity(5); + assertThrows(NullPointerException.class, () -> five.plus(null)); + + //self minus number larger than self -> throws exception + TransactionQuantity six = new TransactionQuantity(6); + assertThrows(IllegalArgumentException.class, () -> five.minus(six)); + + //self minus number smaller than self -> return normal result + TransactionQuantity one = new TransactionQuantity(1); + assertTrue(six.minus(five).equals(one)); + } + + @Test + public void plus() { + //plus null -> throws exception + TransactionQuantity five = new TransactionQuantity(5); + assertThrows(NullPointerException.class, () -> five.plus(null)); + + //self minus self -> return self x 2 + TransactionQuantity ten = new TransactionQuantity(10); + assertTrue(five.plus(five).equals(ten)); + + //self plus other number -> returns quantity with value = self + other + TransactionQuantity six = new TransactionQuantity(6); + TransactionQuantity eleven = new TransactionQuantity(11); + assertTrue(five.plus(six).equals(eleven)); + } +} + diff --git a/src/test/java/seedu/address/model/transaction/TransactionTest.java b/src/test/java/seedu/address/model/transaction/TransactionTest.java new file mode 100644 index 00000000000..d5a6d8ac471 --- /dev/null +++ b/src/test/java/seedu/address/model/transaction/TransactionTest.java @@ -0,0 +1,84 @@ +package seedu.address.model.transaction; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.transaction.TypicalTransactions.ALICE_BUY_ONE_BAG_MARCH_FIRST; +import static seedu.address.testutil.transaction.TypicalTransactions.ALICE_BUY_ONE_BAG_MARCH_SECOND; +import static seedu.address.testutil.transaction.TypicalTransactions.ALICE_BUY_ONE_BOOK_MARCH_FIRST; +import static seedu.address.testutil.transaction.TypicalTransactions.ALICE_BUY_TWO_BAG_MARCH_FIRST; +import static seedu.address.testutil.transaction.TypicalTransactions.BENSON_BUY_ONE_ABACUS_MARCH_FIRST; +import static seedu.address.testutil.transaction.TypicalTransactions.BENSON_BUY_ONE_BAG_MARCH_FIRST; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.transaction.TransactionBuilder; + +public class TransactionTest { + + @Test + public void isSameTransaction() { + //same transaction -> returns true + assertTrue(ALICE_BUY_ONE_BAG_MARCH_FIRST.isSameTransaction(ALICE_BUY_ONE_BAG_MARCH_FIRST)); + + //null -> returns false + assertFalse(ALICE_BUY_ONE_BAG_MARCH_FIRST.isSameTransaction(null)); + + //different quantity while same customer, product, and time -> returns true + assertTrue(ALICE_BUY_ONE_BAG_MARCH_FIRST.isSameTransaction(ALICE_BUY_TWO_BAG_MARCH_FIRST)); + + //different customer and product, while same date -> return false + assertFalse(ALICE_BUY_ONE_BAG_MARCH_FIRST.isSameTransaction(BENSON_BUY_ONE_ABACUS_MARCH_FIRST)); + + //same customer, product, and quantity, while different date -> returns false + assertFalse(ALICE_BUY_ONE_BAG_MARCH_FIRST.isSameTransaction(ALICE_BUY_ONE_BAG_MARCH_SECOND)); + + //same customer, quantity, dateTime, while different product -> returns false + assertFalse(ALICE_BUY_ONE_BAG_MARCH_FIRST.isSameTransaction(ALICE_BUY_ONE_BOOK_MARCH_FIRST)); + + //same customer, product, dateTime, and quantity, while different money -> returns false + Transaction editedTransaction = new TransactionBuilder(ALICE_BUY_ONE_BAG_MARCH_FIRST) + .withMoney(100).build(); + assertTrue(ALICE_BUY_ONE_BAG_MARCH_FIRST.isSameTransaction(editedTransaction)); + + //different description while same other attributes -> returns true + editedTransaction = new TransactionBuilder(ALICE_BUY_ONE_BAG_MARCH_FIRST) + .withDescription("new description").build(); + assertTrue(ALICE_BUY_ONE_BAG_MARCH_FIRST.isSameTransaction(editedTransaction)); + } + + @Test + public void equals() { + //same values -> return true + Transaction aliceCopy = new TransactionBuilder(ALICE_BUY_ONE_BAG_MARCH_FIRST).build(); + assertTrue(ALICE_BUY_ONE_BAG_MARCH_FIRST.equals(aliceCopy)); + + //same object -> returns true + assertTrue(ALICE_BUY_ONE_BAG_MARCH_FIRST.equals(ALICE_BUY_ONE_BAG_MARCH_FIRST)); + + //null -> returns false + assertFalse(ALICE_BUY_ONE_BAG_MARCH_FIRST.equals(null)); + + //different customer -> returns false + assertFalse(ALICE_BUY_ONE_BAG_MARCH_FIRST.equals(BENSON_BUY_ONE_BAG_MARCH_FIRST)); + + //different product -> returns false + assertFalse(ALICE_BUY_ONE_BAG_MARCH_FIRST.equals(ALICE_BUY_ONE_BOOK_MARCH_FIRST)); + + //different dateTime -> returns false + assertFalse(ALICE_BUY_ONE_BAG_MARCH_FIRST.equals(ALICE_BUY_ONE_BAG_MARCH_SECOND)); + + //different quantity -> returns true + assertTrue(ALICE_BUY_ONE_BAG_MARCH_FIRST.equals(ALICE_BUY_TWO_BAG_MARCH_FIRST)); + + //different money -> returns true + Transaction editedTransaction = new TransactionBuilder(ALICE_BUY_ONE_BAG_MARCH_FIRST) + .withMoney(100).build(); + assertTrue(ALICE_BUY_ONE_BAG_MARCH_FIRST.equals(editedTransaction)); + + //different description -> returns true + editedTransaction = new TransactionBuilder(ALICE_BUY_ONE_BAG_MARCH_FIRST) + .withDescription("new description").build(); + assertTrue(ALICE_BUY_ONE_BAG_MARCH_FIRST.equals(editedTransaction)); + + } +} diff --git a/src/test/java/seedu/address/model/util/MoneyTest.java b/src/test/java/seedu/address/model/util/MoneyTest.java new file mode 100644 index 00000000000..07208b93710 --- /dev/null +++ b/src/test/java/seedu/address/model/util/MoneyTest.java @@ -0,0 +1,52 @@ +package seedu.address.model.util; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class MoneyTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Money(null)); + } + + @Test + public void constructor_invalidSales_throwsIllegalArgumentException() { + String invalidSales = ""; + assertThrows(IllegalArgumentException.class, () -> new Money(invalidSales)); + } + + @Test + public void isValidSales() { + // null sales + assertThrows(NullPointerException.class, () -> Money.isValidMoney(null)); + + // invalid sales format + assertFalse(Money.isValidMoney("")); // empty string + assertFalse(Money.isValidMoney(" ")); // spaces only + assertFalse(Money.isValidMoney("price")); // non-numeric + assertFalse(Money.isValidMoney("9011p041")); // alphabets within digits + assertFalse(Money.isValidMoney("9312 1534")); // spaces within digits + assertFalse(Money.isValidMoney("93121534")); // long sales + + // valid sales + assertTrue(Money.isValidMoney("911")); // exactly 3 numbers + assertTrue(Money.isValidMoney("1000000")); + } + + @Test + public void isValidAmount() { + // invalid amount + assertFalse(Money.isValidAmount((93121534))); // long amount + assertFalse(Money.isValidAmount((10000000))); // long amount + assertFalse(Money.isValidAmount((1000001))); // long amount + + // valid amount + assertTrue(Money.isValidAmount((0))); // 0 amount + assertTrue(Money.isValidAmount((999999))); // almost exceeds limit. + + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedCustomerTest.java similarity index 59% rename from src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java rename to src/test/java/seedu/address/storage/JsonAdaptedCustomerTest.java index 83b11331cdb..f81ea842fe4 100644 --- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java +++ b/src/test/java/seedu/address/storage/JsonAdaptedCustomerTest.java @@ -1,9 +1,9 @@ package seedu.address.storage; import static org.junit.jupiter.api.Assertions.assertEquals; -import static seedu.address.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.storage.customer.JsonAdaptedCustomer.MISSING_FIELD_MESSAGE_FORMAT; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.customer.TypicalCustomers.BENSON; import java.util.ArrayList; import java.util.List; @@ -12,12 +12,14 @@ import org.junit.jupiter.api.Test; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; - -public class JsonAdaptedPersonTest { +import seedu.address.model.customer.Address; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; +import seedu.address.storage.customer.JsonAdaptedCustomer; + +public class JsonAdaptedCustomerTest { + private static final String VALID_ID = "a4365691-ea10-47ad-b33b-fc038f1e5e81"; private static final String INVALID_NAME = "R@chel"; private static final String INVALID_PHONE = "+651234"; private static final String INVALID_ADDRESS = " "; @@ -34,66 +36,82 @@ public class JsonAdaptedPersonTest { @Test public void toModelType_validPersonDetails_returnsPerson() throws Exception { - JsonAdaptedPerson person = new JsonAdaptedPerson(BENSON); + JsonAdaptedCustomer person = new JsonAdaptedCustomer(BENSON); assertEquals(BENSON, person.toModelType()); } + @Test + public void toModelType_nullId_throwsIllegalValueException() { + JsonAdaptedCustomer person = new JsonAdaptedCustomer(null, VALID_NAME, VALID_PHONE, + VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, String.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + @Test public void toModelType_invalidName_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedCustomer person = + new JsonAdaptedCustomer(VALID_ID, INVALID_NAME, VALID_PHONE, VALID_EMAIL, + VALID_ADDRESS, VALID_TAGS); String expectedMessage = Name.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullName_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedCustomer person = new JsonAdaptedCustomer(VALID_ID, null, VALID_PHONE, + VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_invalidPhone_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedCustomer person = + new JsonAdaptedCustomer(VALID_ID, VALID_NAME, INVALID_PHONE, VALID_EMAIL, + VALID_ADDRESS, VALID_TAGS); String expectedMessage = Phone.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullPhone_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedCustomer person = new JsonAdaptedCustomer(VALID_ID, VALID_NAME, null, + VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_invalidEmail_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedCustomer person = + new JsonAdaptedCustomer(VALID_ID, VALID_NAME, VALID_PHONE, INVALID_EMAIL, + VALID_ADDRESS, VALID_TAGS); String expectedMessage = Email.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullEmail_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedCustomer person = new JsonAdaptedCustomer(VALID_ID, VALID_NAME, + VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_invalidAddress_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS); + JsonAdaptedCustomer person = + new JsonAdaptedCustomer(VALID_ID, VALID_NAME, VALID_PHONE, VALID_EMAIL, + INVALID_ADDRESS, VALID_TAGS); String expectedMessage = Address.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullAddress_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS); + JsonAdaptedCustomer person = new JsonAdaptedCustomer(VALID_ID, VALID_NAME, VALID_PHONE, + VALID_EMAIL, null, VALID_TAGS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -102,8 +120,9 @@ public void toModelType_nullAddress_throwsIllegalValueException() { public void toModelType_invalidTags_throwsIllegalValueException() { List invalidTags = new ArrayList<>(VALID_TAGS); invalidTags.add(new JsonAdaptedTag(INVALID_TAG)); - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags); + JsonAdaptedCustomer person = + new JsonAdaptedCustomer(VALID_ID, VALID_NAME, VALID_PHONE, VALID_EMAIL, + VALID_ADDRESS, invalidTags); assertThrows(IllegalValueException.class, person::toModelType); } diff --git a/src/test/java/seedu/address/storage/JsonAdaptedProductTest.java b/src/test/java/seedu/address/storage/JsonAdaptedProductTest.java new file mode 100644 index 00000000000..6a0244b26ea --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedProductTest.java @@ -0,0 +1,114 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.storage.product.JsonAdaptedProduct.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.product.TypicalProducts.BAG; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.product.CostPrice; +import seedu.address.model.product.Price; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; +import seedu.address.model.util.QuantityThreshold; +import seedu.address.storage.product.JsonAdaptedProduct; + +public class JsonAdaptedProductTest { + private static final String INVALID_COSTPRICE = "-12345"; + private static final String INVALID_PRICE = "+651234"; + private static final String INVALID_QUANTITY = " "; + private static final String INVALID_SALES = "example.com"; + + private static final String VALID_DESCRIPTION = BAG.getDescription().toString(); + private static final String VALID_COSTPRICE = BAG.getCostPrice().toString(); + private static final String VALID_PRICE = BAG.getPrice().toString(); + private static final String VALID_QUANTITY = BAG.getQuantity().toString(); + private static final String VALID_SALES = BAG.getMoney().toString(); + private static final String VALID_THRESHOLD = BAG.getThreshold().toString(); + private static final String VALID_ID = BAG.getId().toString(); + + @Test + public void toModelType_validProductDetails_returnsProduct() throws Exception { + JsonAdaptedProduct bag = new JsonAdaptedProduct(BAG); + assertEquals(BAG, bag.toModelType()); + } + + @Test + public void toModelType_nullDescription_throwsIllegalValueException() { + JsonAdaptedProduct product = new JsonAdaptedProduct(null, VALID_COSTPRICE, VALID_PRICE, + VALID_QUANTITY, VALID_SALES, VALID_THRESHOLD, VALID_ID); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Description.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, product::toModelType); + } + + @Test + public void toModelType_invalidCostPrice_throwsIllegalValueException() { + JsonAdaptedProduct product = + new JsonAdaptedProduct(VALID_DESCRIPTION, INVALID_COSTPRICE, VALID_PRICE, VALID_QUANTITY, + VALID_SALES, VALID_THRESHOLD, VALID_ID); + String expectedMessage = CostPrice.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, product::toModelType); + } + + @Test + public void toModelType_invalidPrice_throwsIllegalValueException() { + JsonAdaptedProduct product = + new JsonAdaptedProduct(VALID_DESCRIPTION, VALID_COSTPRICE, INVALID_PRICE, VALID_QUANTITY, + VALID_SALES, VALID_THRESHOLD, VALID_ID); + String expectedMessage = Price.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, product::toModelType); + } + + @Test + public void toModelType_nullPrice_throwsIllegalValueException() { + JsonAdaptedProduct product = new JsonAdaptedProduct(VALID_DESCRIPTION, VALID_COSTPRICE, null, + VALID_QUANTITY, VALID_SALES, VALID_THRESHOLD, VALID_ID); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Price.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, product::toModelType); + } + + @Test + public void toModelType_invalidQuantity_throwsIllegalValueException() { + JsonAdaptedProduct product = + new JsonAdaptedProduct(VALID_DESCRIPTION, VALID_COSTPRICE, VALID_PRICE, + INVALID_QUANTITY, VALID_SALES, VALID_THRESHOLD, VALID_ID); + String expectedMessage = Quantity.MESSAGE_CONSTRAINTS_FORMAT; + assertThrows(IllegalValueException.class, expectedMessage, product::toModelType); + } + + @Test + public void toModelType_nullQuantity_throwsIllegalValueException() { + JsonAdaptedProduct product = new JsonAdaptedProduct(VALID_DESCRIPTION, VALID_COSTPRICE, VALID_PRICE, null, + VALID_SALES, VALID_THRESHOLD, VALID_ID); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Quantity.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, product::toModelType); + } + + @Test + public void toModelType_invalidSales_throwsIllegalValueException() { + JsonAdaptedProduct product = + new JsonAdaptedProduct(VALID_DESCRIPTION, VALID_COSTPRICE, VALID_PRICE, + VALID_QUANTITY, INVALID_SALES, VALID_THRESHOLD, VALID_ID); + String expectedMessage = Money.MESSAGE_CONSTRAINTS_FORMAT; + assertThrows(IllegalValueException.class, expectedMessage, product::toModelType); + } + + @Test + public void toModelType_nullSales_throwsIllegalValueException() { + JsonAdaptedProduct product = new JsonAdaptedProduct(VALID_DESCRIPTION, VALID_COSTPRICE, VALID_PRICE, + VALID_QUANTITY, null, VALID_THRESHOLD, VALID_ID); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Money.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, product::toModelType); + } + + @Test + public void toModelType_nullThreshold_throwsIllegalValueException() { + JsonAdaptedProduct product = new JsonAdaptedProduct(VALID_DESCRIPTION, VALID_COSTPRICE, VALID_PRICE, + VALID_QUANTITY, VALID_SALES, null, VALID_ID); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, QuantityThreshold.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, product::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonInventorySystemStorageTest.java similarity index 59% rename from src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java rename to src/test/java/seedu/address/storage/JsonInventorySystemStorageTest.java index ac3c3af9566..9af0333be60 100644 --- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java +++ b/src/test/java/seedu/address/storage/JsonInventorySystemStorageTest.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.HOON; -import static seedu.address.testutil.TypicalPersons.IDA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.customer.TypicalCustomers.ALICE; +import static seedu.address.testutil.customer.TypicalCustomers.HOON; +import static seedu.address.testutil.customer.TypicalCustomers.IDA; +import static seedu.address.testutil.customer.TypicalCustomers.getTypicalInventorySystem; import java.io.IOException; import java.nio.file.Path; @@ -16,11 +16,11 @@ import org.junit.jupiter.api.io.TempDir; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.InventorySystem; +import seedu.address.model.ReadOnlyInventorySystem; -public class JsonAddressBookStorageTest { - private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonAddressBookStorageTest"); +public class JsonInventorySystemStorageTest { + private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonInventorySystemStorageTest"); @TempDir public Path testFolder; @@ -30,8 +30,9 @@ public void readAddressBook_nullFilePath_throwsNullPointerException() { assertThrows(NullPointerException.class, () -> readAddressBook(null)); } - private java.util.Optional readAddressBook(String filePath) throws Exception { - return new JsonAddressBookStorage(Paths.get(filePath)).readAddressBook(addToTestDataPathIfNotNull(filePath)); + private java.util.Optional readAddressBook(String filePath) throws Exception { + return new JsonInventorySystemStorage(Paths.get(filePath)) + .readInventorySystem(addToTestDataPathIfNotNull(filePath)); } private Path addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { @@ -63,26 +64,26 @@ public void readAddressBook_invalidAndValidPersonAddressBook_throwDataConversion @Test public void readAndSaveAddressBook_allInOrder_success() throws Exception { Path filePath = testFolder.resolve("TempAddressBook.json"); - AddressBook original = getTypicalAddressBook(); - JsonAddressBookStorage jsonAddressBookStorage = new JsonAddressBookStorage(filePath); + InventorySystem original = getTypicalInventorySystem(); + JsonInventorySystemStorage jsonAddressBookStorage = new JsonInventorySystemStorage(filePath); // Save in new file and read back - jsonAddressBookStorage.saveAddressBook(original, filePath); - ReadOnlyAddressBook readBack = jsonAddressBookStorage.readAddressBook(filePath).get(); - assertEquals(original, new AddressBook(readBack)); + jsonAddressBookStorage.saveInventorySystem(original, filePath); + ReadOnlyInventorySystem readBack = jsonAddressBookStorage.readInventorySystem(filePath).get(); + assertEquals(original, new InventorySystem(readBack)); // Modify data, overwrite exiting file, and read back original.addPerson(HOON); original.removePerson(ALICE); - jsonAddressBookStorage.saveAddressBook(original, filePath); - readBack = jsonAddressBookStorage.readAddressBook(filePath).get(); - assertEquals(original, new AddressBook(readBack)); + jsonAddressBookStorage.saveInventorySystem(original, filePath); + readBack = jsonAddressBookStorage.readInventorySystem(filePath).get(); + assertEquals(original, new InventorySystem(readBack)); // Save and read without specifying file path original.addPerson(IDA); - jsonAddressBookStorage.saveAddressBook(original); // file path not specified - readBack = jsonAddressBookStorage.readAddressBook().get(); // file path not specified - assertEquals(original, new AddressBook(readBack)); + jsonAddressBookStorage.saveInventorySystem(original); // file path not specified + readBack = jsonAddressBookStorage.readInventorySystem().get(); // file path not specified + assertEquals(original, new InventorySystem(readBack)); } @@ -94,10 +95,10 @@ public void saveAddressBook_nullAddressBook_throwsNullPointerException() { /** * Saves {@code addressBook} at the specified {@code filePath}. */ - private void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) { + private void saveAddressBook(ReadOnlyInventorySystem addressBook, String filePath) { try { - new JsonAddressBookStorage(Paths.get(filePath)) - .saveAddressBook(addressBook, addToTestDataPathIfNotNull(filePath)); + new JsonInventorySystemStorage(Paths.get(filePath)) + .saveInventorySystem(addressBook, addToTestDataPathIfNotNull(filePath)); } catch (IOException ioe) { throw new AssertionError("There should not be an error writing to the file.", ioe); } @@ -105,6 +106,6 @@ private void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) { @Test public void saveAddressBook_nullFilePath_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> saveAddressBook(new AddressBook(), null)); + assertThrows(NullPointerException.class, () -> saveAddressBook(new InventorySystem(), null)); } } diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableInventorySystemTest.java similarity index 56% rename from src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java rename to src/test/java/seedu/address/storage/JsonSerializableInventorySystemTest.java index 188c9058d20..46925392d11 100644 --- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java +++ b/src/test/java/seedu/address/storage/JsonSerializableInventorySystemTest.java @@ -10,37 +10,38 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.JsonUtil; -import seedu.address.model.AddressBook; -import seedu.address.testutil.TypicalPersons; +import seedu.address.model.InventorySystem; +import seedu.address.testutil.customer.TypicalCustomers; -public class JsonSerializableAddressBookTest { +public class JsonSerializableInventorySystemTest { - private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableAddressBookTest"); + private static final Path TEST_DATA_FOLDER = + Paths.get("src", "test", "data", "JsonSerializableInventorySystemTest"); private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsAddressBook.json"); private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonAddressBook.json"); private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonAddressBook.json"); @Test public void toModelType_typicalPersonsFile_success() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE, - JsonSerializableAddressBook.class).get(); - AddressBook addressBookFromFile = dataFromFile.toModelType(); - AddressBook typicalPersonsAddressBook = TypicalPersons.getTypicalAddressBook(); + JsonSerializableInventorySystem dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE, + JsonSerializableInventorySystem.class).get(); + InventorySystem addressBookFromFile = dataFromFile.toModelType(); + InventorySystem typicalPersonsAddressBook = TypicalCustomers.getTypicalInventorySystem(); assertEquals(addressBookFromFile, typicalPersonsAddressBook); } @Test public void toModelType_invalidPersonFile_throwsIllegalValueException() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_PERSON_FILE, - JsonSerializableAddressBook.class).get(); + JsonSerializableInventorySystem dataFromFile = JsonUtil.readJsonFile(INVALID_PERSON_FILE, + JsonSerializableInventorySystem.class).get(); assertThrows(IllegalValueException.class, dataFromFile::toModelType); } @Test public void toModelType_duplicatePersons_throwsIllegalValueException() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_PERSON_FILE, - JsonSerializableAddressBook.class).get(); - assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_PERSON, + JsonSerializableInventorySystem dataFromFile = JsonUtil.readJsonFile(DUPLICATE_PERSON_FILE, + JsonSerializableInventorySystem.class).get(); + assertThrows(IllegalValueException.class, JsonSerializableInventorySystem.MESSAGE_DUPLICATE_PERSON, dataFromFile::toModelType); } diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java index 99a16548970..53a720c130e 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/seedu/address/storage/StorageManagerTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.customer.TypicalCustomers.getTypicalInventorySystem; import java.nio.file.Path; @@ -11,8 +11,8 @@ import org.junit.jupiter.api.io.TempDir; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.InventorySystem; +import seedu.address.model.ReadOnlyInventorySystem; import seedu.address.model.UserPrefs; public class StorageManagerTest { @@ -24,7 +24,7 @@ public class StorageManagerTest { @BeforeEach public void setUp() { - JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage(getTempFilePath("ab")); + JsonInventorySystemStorage addressBookStorage = new JsonInventorySystemStorage(getTempFilePath("ab")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(getTempFilePath("prefs")); storageManager = new StorageManager(addressBookStorage, userPrefsStorage); } @@ -51,18 +51,18 @@ public void prefsReadSave() throws Exception { public void addressBookReadSave() throws Exception { /* * Note: This is an integration test that verifies the StorageManager is properly wired to the - * {@link JsonAddressBookStorage} class. - * More extensive testing of UserPref saving/reading is done in {@link JsonAddressBookStorageTest} class. + * {@link JsonInventorySystemStorage} class. + * More extensive testing of UserPref saving/reading is done in {@link JsonInventorySystemStorageTest} class. */ - AddressBook original = getTypicalAddressBook(); - storageManager.saveAddressBook(original); - ReadOnlyAddressBook retrieved = storageManager.readAddressBook().get(); - assertEquals(original, new AddressBook(retrieved)); + InventorySystem original = getTypicalInventorySystem(); + storageManager.saveInventorySystem(original); + ReadOnlyInventorySystem retrieved = storageManager.readInventorySystem().get(); + assertEquals(original, new InventorySystem(retrieved)); } @Test public void getAddressBookFilePath() { - assertNotNull(storageManager.getAddressBookFilePath()); + assertNotNull(storageManager.getInventorySystemFilePath()); } } diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java deleted file mode 100644 index d53799fd110..00000000000 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ /dev/null @@ -1,34 +0,0 @@ -package seedu.address.testutil; - -import seedu.address.model.AddressBook; -import seedu.address.model.person.Person; - -/** - * A utility class to help with building Addressbook objects. - * Example usage:
- * {@code AddressBook ab = new AddressBookBuilder().withPerson("John", "Doe").build();} - */ -public class AddressBookBuilder { - - private AddressBook addressBook; - - public AddressBookBuilder() { - addressBook = new AddressBook(); - } - - public AddressBookBuilder(AddressBook addressBook) { - this.addressBook = addressBook; - } - - /** - * Adds a new {@code Person} to the {@code AddressBook} that we are building. - */ - public AddressBookBuilder withPerson(Person person) { - addressBook.addPerson(person); - return this; - } - - public AddressBook build() { - return addressBook; - } -} diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java deleted file mode 100644 index 4584bd5044e..00000000000 --- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.testutil; - -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * A utility class to help with building EditPersonDescriptor objects. - */ -public class EditPersonDescriptorBuilder { - - private EditPersonDescriptor descriptor; - - public EditPersonDescriptorBuilder() { - descriptor = new EditPersonDescriptor(); - } - - public EditPersonDescriptorBuilder(EditPersonDescriptor descriptor) { - this.descriptor = new EditPersonDescriptor(descriptor); - } - - /** - * Returns an {@code EditPersonDescriptor} with fields containing {@code person}'s details - */ - public EditPersonDescriptorBuilder(Person person) { - descriptor = new EditPersonDescriptor(); - descriptor.setName(person.getName()); - descriptor.setPhone(person.getPhone()); - descriptor.setEmail(person.getEmail()); - descriptor.setAddress(person.getAddress()); - descriptor.setTags(person.getTags()); - } - - /** - * Sets the {@code Name} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withName(String name) { - descriptor.setName(new Name(name)); - return this; - } - - /** - * Sets the {@code Phone} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withPhone(String phone) { - descriptor.setPhone(new Phone(phone)); - return this; - } - - /** - * Sets the {@code Email} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withEmail(String email) { - descriptor.setEmail(new Email(email)); - return this; - } - - /** - * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. - */ - public EditPersonDescriptorBuilder withAddress(String address) { - descriptor.setAddress(new Address(address)); - return this; - } - - /** - * Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor} - * that we are building. - */ - public EditPersonDescriptorBuilder withTags(String... tags) { - Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet()); - descriptor.setTags(tagSet); - return this; - } - - public EditPersonDescriptor build() { - return descriptor; - } -} diff --git a/src/test/java/seedu/address/testutil/InventorySystemBuilder.java b/src/test/java/seedu/address/testutil/InventorySystemBuilder.java new file mode 100644 index 00000000000..22adfda3a0e --- /dev/null +++ b/src/test/java/seedu/address/testutil/InventorySystemBuilder.java @@ -0,0 +1,34 @@ +package seedu.address.testutil; + +import seedu.address.model.InventorySystem; +import seedu.address.model.customer.Customer; + +/** + * A utility class to help with building InventorySystem objects. + * Example usage:
+ * {@code InventorySystem ab = new InventorySystemBuilder().withPerson("John", "Doe").build();} + */ +public class InventorySystemBuilder { + + private InventorySystem inventorySystem; + + public InventorySystemBuilder() { + inventorySystem = new InventorySystem(); + } + + public InventorySystemBuilder(InventorySystem inventorySystem) { + this.inventorySystem = inventorySystem; + } + + /** + * Adds a new {@code Customer} to the {@code InventorySystem} that we are building. + */ + public InventorySystemBuilder withPerson(Customer customer) { + inventorySystem.addPerson(customer); + return this; + } + + public InventorySystem build() { + return inventorySystem; + } +} diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java deleted file mode 100644 index 5eff412178b..00000000000 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ /dev/null @@ -1,93 +0,0 @@ -package seedu.address.testutil; - -import java.util.HashSet; -import java.util.Set; - -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; -import seedu.address.model.util.SampleDataUtil; - -/** - * A utility class to help with building Person objects. - */ -public class PersonBuilder { - - public static final String DEFAULT_NAME = "Alice Pauline"; - public static final String DEFAULT_PHONE = "85355255"; - public static final String DEFAULT_EMAIL = "alice@gmail.com"; - public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; - - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public PersonBuilder() { - name = new Name(DEFAULT_NAME); - phone = new Phone(DEFAULT_PHONE); - email = new Email(DEFAULT_EMAIL); - address = new Address(DEFAULT_ADDRESS); - tags = new HashSet<>(); - } - - /** - * Initializes the PersonBuilder with the data of {@code personToCopy}. - */ - public PersonBuilder(Person personToCopy) { - name = personToCopy.getName(); - phone = personToCopy.getPhone(); - email = personToCopy.getEmail(); - address = personToCopy.getAddress(); - tags = new HashSet<>(personToCopy.getTags()); - } - - /** - * Sets the {@code Name} of the {@code Person} that we are building. - */ - public PersonBuilder withName(String name) { - this.name = new Name(name); - return this; - } - - /** - * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building. - */ - public PersonBuilder withTags(String ... tags) { - this.tags = SampleDataUtil.getTagSet(tags); - return this; - } - - /** - * Sets the {@code Address} of the {@code Person} that we are building. - */ - public PersonBuilder withAddress(String address) { - this.address = new Address(address); - return this; - } - - /** - * Sets the {@code Phone} of the {@code Person} that we are building. - */ - public PersonBuilder withPhone(String phone) { - this.phone = new Phone(phone); - return this; - } - - /** - * Sets the {@code Email} of the {@code Person} that we are building. - */ - public PersonBuilder withEmail(String email) { - this.email = new Email(email); - return this; - } - - public Person build() { - return new Person(name, phone, email, address, tags); - } - -} diff --git a/src/test/java/seedu/address/testutil/TestUtil.java b/src/test/java/seedu/address/testutil/TestUtil.java index 896d103eb0b..658577b7838 100644 --- a/src/test/java/seedu/address/testutil/TestUtil.java +++ b/src/test/java/seedu/address/testutil/TestUtil.java @@ -7,7 +7,7 @@ import seedu.address.commons.core.index.Index; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.customer.Customer; /** * A utility class for test cases. @@ -33,23 +33,23 @@ public static Path getFilePathInSandboxFolder(String fileName) { } /** - * Returns the middle index of the person in the {@code model}'s person list. + * Returns the middle index of the customer in the {@code model}'s customer list. */ public static Index getMidIndex(Model model) { - return Index.fromOneBased(model.getFilteredPersonList().size() / 2); + return Index.fromOneBased(model.getFilteredCustomerList().size() / 2); } /** - * Returns the last index of the person in the {@code model}'s person list. + * Returns the last index of the customer in the {@code model}'s customer list. */ public static Index getLastIndex(Model model) { - return Index.fromOneBased(model.getFilteredPersonList().size()); + return Index.fromOneBased(model.getFilteredCustomerList().size()); } /** - * Returns the person in the {@code model}'s person list at {@code index}. + * Returns the customer in the {@code model}'s customer list at {@code index}. */ - public static Person getPerson(Model model, Index index) { - return model.getFilteredPersonList().get(index.getZeroBased()); + public static Customer getPerson(Model model, Index index) { + return model.getFilteredCustomerList().get(index.getZeroBased()); } } diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java index 1e613937657..353fb2d69a3 100644 --- a/src/test/java/seedu/address/testutil/TypicalIndexes.java +++ b/src/test/java/seedu/address/testutil/TypicalIndexes.java @@ -9,4 +9,8 @@ public class TypicalIndexes { public static final Index INDEX_FIRST_PERSON = Index.fromOneBased(1); public static final Index INDEX_SECOND_PERSON = Index.fromOneBased(2); public static final Index INDEX_THIRD_PERSON = Index.fromOneBased(3); + public static final Index INDEX_FIRST_PRODUCT = Index.fromOneBased(1); + public static final Index INDEX_SECOND_PRODUCT = Index.fromOneBased(2); + public static final Index INDEX_THIRD_PRODUCT = Index.fromOneBased(3); + public static final Index INDEX_FIRST_TRANSACTION = Index.fromOneBased(1); } diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java deleted file mode 100644 index fec76fb7129..00000000000 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.testutil; - -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import seedu.address.model.AddressBook; -import seedu.address.model.person.Person; - -/** - * A utility class containing a list of {@code Person} objects to be used in tests. - */ -public class TypicalPersons { - - public static final Person ALICE = new PersonBuilder().withName("Alice Pauline") - .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") - .withPhone("94351253") - .withTags("friends").build(); - public static final Person BENSON = new PersonBuilder().withName("Benson Meier") - .withAddress("311, Clementi Ave 2, #02-25") - .withEmail("johnd@example.com").withPhone("98765432") - .withTags("owesMoney", "friends").build(); - public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") - .withEmail("heinz@example.com").withAddress("wall street").build(); - public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") - .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build(); - public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") - .withEmail("werner@example.com").withAddress("michegan ave").build(); - public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") - .withEmail("lydia@example.com").withAddress("little tokyo").build(); - public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442") - .withEmail("anna@example.com").withAddress("4th street").build(); - - // Manually added - public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") - .withEmail("stefan@example.com").withAddress("little india").build(); - public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") - .withEmail("hans@example.com").withAddress("chicago ave").build(); - - // Manually added - Person's details found in {@code CommandTestUtil} - public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) - .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); - public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) - .build(); - - public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER - - private TypicalPersons() {} // prevents instantiation - - /** - * Returns an {@code AddressBook} with all the typical persons. - */ - public static AddressBook getTypicalAddressBook() { - AddressBook ab = new AddressBook(); - for (Person person : getTypicalPersons()) { - ab.addPerson(person); - } - return ab; - } - - public static List getTypicalPersons() { - return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); - } -} diff --git a/src/test/java/seedu/address/testutil/customer/CustomerBuilder.java b/src/test/java/seedu/address/testutil/customer/CustomerBuilder.java new file mode 100644 index 00000000000..fdcb61f503c --- /dev/null +++ b/src/test/java/seedu/address/testutil/customer/CustomerBuilder.java @@ -0,0 +1,105 @@ +package seedu.address.testutil.customer; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import seedu.address.model.customer.Address; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; +import seedu.address.model.tag.Tag; +import seedu.address.model.util.SampleDataUtil; + +/** + * A utility class to help with building Customer objects. + */ +public class CustomerBuilder { + + public static final String DEFAULT_NAME = "Alice Pauline"; + public static final String DEFAULT_PHONE = "85355255"; + public static final String DEFAULT_EMAIL = "alice@gmail.com"; + public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; + + private UUID id; + private Name name; + private Phone phone; + private Email email; + private Address address; + private Set tags; + + public CustomerBuilder(String id) { + this.id = UUID.fromString(id); + name = new Name(DEFAULT_NAME); + phone = new Phone(DEFAULT_PHONE); + email = new Email(DEFAULT_EMAIL); + address = new Address(DEFAULT_ADDRESS); + tags = new HashSet<>(); + } + + /** + * Initializes the CustomerBuilder with the data of {@code customerToCopy}. + */ + public CustomerBuilder(Customer customerToCopy) { + id = customerToCopy.getId(); + name = customerToCopy.getName(); + phone = customerToCopy.getPhone(); + email = customerToCopy.getEmail(); + address = customerToCopy.getAddress(); + tags = new HashSet<>(customerToCopy.getTags()); + } + + /** + * Sets the {@code UUID} of the {@code Customer} that we are building. + */ + public CustomerBuilder withId(String id) { + this.id = UUID.fromString(id); + return this; + } + + /** + * Sets the {@code Name} of the {@code Customer} that we are building. + */ + public CustomerBuilder withName(String name) { + this.name = new Name(name); + return this; + } + + /** + * Parses the {@code tags} into a {@code Set} and set it to the {@code Customer} that we are building. + */ + public CustomerBuilder withTags(String ... tags) { + this.tags = SampleDataUtil.getTagSet(tags); + return this; + } + + /** + * Sets the {@code Address} of the {@code Customer} that we are building. + */ + public CustomerBuilder withAddress(String address) { + this.address = new Address(address); + return this; + } + + /** + * Sets the {@code Phone} of the {@code Customer} that we are building. + */ + public CustomerBuilder withPhone(String phone) { + this.phone = new Phone(phone); + return this; + } + + /** + * Sets the {@code Email} of the {@code Customer} that we are building. + */ + public CustomerBuilder withEmail(String email) { + this.email = new Email(email); + return this; + } + + public Customer build() { + return new Customer(id, name, phone, email, address, tags); + } + +} diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/customer/CustomerUtil.java similarity index 57% rename from src/test/java/seedu/address/testutil/PersonUtil.java rename to src/test/java/seedu/address/testutil/customer/CustomerUtil.java index 90849945183..48ff6dc804c 100644 --- a/src/test/java/seedu/address/testutil/PersonUtil.java +++ b/src/test/java/seedu/address/testutil/customer/CustomerUtil.java @@ -1,4 +1,4 @@ -package seedu.address.testutil; +package seedu.address.testutil.customer; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; @@ -8,42 +8,42 @@ import java.util.Set; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Person; +import seedu.address.logic.commands.customer.AddCustomerCommand; +import seedu.address.logic.commands.customer.EditCustomerCommand.EditCustomerDescriptor; +import seedu.address.model.customer.Customer; import seedu.address.model.tag.Tag; /** - * A utility class for Person. + * A utility class for Customer. */ -public class PersonUtil { +public class CustomerUtil { /** - * Returns an add command string for adding the {@code person}. + * Returns an add command string for adding the {@code customer}. */ - public static String getAddCommand(Person person) { - return AddCommand.COMMAND_WORD + " " + getPersonDetails(person); + public static String getAddCommand(Customer customer) { + return AddCustomerCommand.COMMAND_WORD + " " + getPersonDetails(customer); } /** - * Returns the part of command string for the given {@code person}'s details. + * Returns the part of command string for the given {@code customer}'s details. */ - public static String getPersonDetails(Person person) { + public static String getPersonDetails(Customer customer) { StringBuilder sb = new StringBuilder(); - sb.append(PREFIX_NAME + person.getName().fullName + " "); - sb.append(PREFIX_PHONE + person.getPhone().value + " "); - sb.append(PREFIX_EMAIL + person.getEmail().value + " "); - sb.append(PREFIX_ADDRESS + person.getAddress().value + " "); - person.getTags().stream().forEach( + sb.append(PREFIX_NAME + customer.getName().fullName + " "); + sb.append(PREFIX_PHONE + customer.getPhone().value + " "); + sb.append(PREFIX_EMAIL + customer.getEmail().value + " "); + sb.append(PREFIX_ADDRESS + customer.getAddress().value + " "); + customer.getTags().stream().forEach( s -> sb.append(PREFIX_TAG + s.tagName + " ") ); return sb.toString(); } /** - * Returns the part of command string for the given {@code EditPersonDescriptor}'s details. + * Returns the part of command string for the given {@code EditCustomerDescriptor}'s details. */ - public static String getEditPersonDescriptorDetails(EditPersonDescriptor descriptor) { + public static String getEditPersonDescriptorDetails(EditCustomerDescriptor descriptor) { StringBuilder sb = new StringBuilder(); descriptor.getName().ifPresent(name -> sb.append(PREFIX_NAME).append(name.fullName).append(" ")); descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" ")); diff --git a/src/test/java/seedu/address/testutil/customer/EditCustomerDescriptorBuilder.java b/src/test/java/seedu/address/testutil/customer/EditCustomerDescriptorBuilder.java new file mode 100644 index 00000000000..262b87ad603 --- /dev/null +++ b/src/test/java/seedu/address/testutil/customer/EditCustomerDescriptorBuilder.java @@ -0,0 +1,98 @@ +package seedu.address.testutil.customer; + +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import seedu.address.logic.commands.customer.EditCustomerCommand; +import seedu.address.logic.commands.customer.EditCustomerCommand.EditCustomerDescriptor; +import seedu.address.model.customer.Address; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; +import seedu.address.model.tag.Tag; + +/** + * A utility class to help with building EditCustomerDescriptor objects. + */ +public class EditCustomerDescriptorBuilder { + + private EditCustomerCommand.EditCustomerDescriptor descriptor; + + public EditCustomerDescriptorBuilder() { + descriptor = new EditCustomerDescriptor(); + } + + public EditCustomerDescriptorBuilder(EditCustomerDescriptor descriptor) { + this.descriptor = new EditCustomerDescriptor(descriptor); + } + + /** + * Returns an {@code EditCustomerDescriptor} with fields containing {@code customer}'s details + */ + public EditCustomerDescriptorBuilder(Customer customer) { + descriptor = new EditCustomerDescriptor(); + descriptor.setId(customer.getId()); + descriptor.setName(customer.getName()); + descriptor.setPhone(customer.getPhone()); + descriptor.setEmail(customer.getEmail()); + descriptor.setAddress(customer.getAddress()); + descriptor.setTags(customer.getTags()); + } + + /** + * Sets the {@code UUID} of the {@code EditCustomerDescriptor} that we are building. + */ + public EditCustomerDescriptorBuilder withId(UUID id) { + descriptor.setId(id); + return this; + } + + /** + * Sets the {@code Name} of the {@code EditCustomerDescriptor} that we are building. + */ + public EditCustomerDescriptorBuilder withName(String name) { + descriptor.setName(new Name(name)); + return this; + } + + /** + * Sets the {@code Phone} of the {@code EditCustomerDescriptor} that we are building. + */ + public EditCustomerDescriptorBuilder withPhone(String phone) { + descriptor.setPhone(new Phone(phone)); + return this; + } + + /** + * Sets the {@code Email} of the {@code EditCustomerDescriptor} that we are building. + */ + public EditCustomerDescriptorBuilder withEmail(String email) { + descriptor.setEmail(new Email(email)); + return this; + } + + /** + * Sets the {@code Address} of the {@code EditCustomerDescriptor} that we are building. + */ + public EditCustomerDescriptorBuilder withAddress(String address) { + descriptor.setAddress(new Address(address)); + return this; + } + + /** + * Parses the {@code tags} into a {@code Set} and set it to the {@code EditCustomerDescriptor} + * that we are building. + */ + public EditCustomerDescriptorBuilder withTags(String... tags) { + Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet()); + descriptor.setTags(tagSet); + return this; + } + + public EditCustomerCommand.EditCustomerDescriptor build() { + return descriptor; + } +} diff --git a/src/test/java/seedu/address/testutil/customer/TypicalCustomers.java b/src/test/java/seedu/address/testutil/customer/TypicalCustomers.java new file mode 100644 index 00000000000..b0ed83462c1 --- /dev/null +++ b/src/test/java/seedu/address/testutil/customer/TypicalCustomers.java @@ -0,0 +1,89 @@ +package seedu.address.testutil.customer; + +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.InventorySystem; +import seedu.address.model.customer.Customer; + +/** + * A utility class containing a list of {@code Customer} objects to be used in tests. + */ +public class TypicalCustomers { + + public static final String ALICE_ID = "a4365691-ea10-47ad-b33b-fc038f1e5e81"; + public static final String BOB_ID = "a4365692-ea10-47ad-b33b-fc038f1e5e81"; + public static final String CARL_ID = "a4365693-ea10-47ad-b33b-fc038f1e5e81"; + public static final String DANIEL_ID = "a4365694-ea10-47ad-b33b-fc038f1e5e81"; + public static final String ELLE_ID = "a4365695-ea10-47ad-b33b-fc038f1e5e81"; + public static final String FIONA_ID = "a4365696-ea10-47ad-b33b-fc038f1e5e81"; + public static final String GEORGE_ID = "a4365697-ea10-47ad-b33b-fc038f1e5e81"; + public static final String HOON_ID = "a4365698-ea10-47ad-b33b-fc038f1e5e81"; + public static final String IDA_ID = "a4365699-ea10-47ad-b33b-fc038f1e5e81"; + public static final String AMY_ID = "a4365680-ea10-47ad-b33b-fc038f1e5e81"; + + public static final Customer ALICE = new CustomerBuilder(ALICE_ID).withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + .withPhone("94351253") + .withTags("friends").build(); + public static final Customer BENSON = new CustomerBuilder(BOB_ID).withName("Benson Meier") + .withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withPhone("98765432") + .withTags("owesMoney", "friends").build(); + public static final Customer CARL = new CustomerBuilder(CARL_ID).withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withAddress("wall street").build(); + public static final Customer DANIEL = new CustomerBuilder(DANIEL_ID).withName("Daniel Meier").withPhone("87652533") + .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build(); + public static final Customer ELLE = new CustomerBuilder(ELLE_ID).withName("Elle Meyer").withPhone("9482224") + .withEmail("werner@example.com").withAddress("michegan ave").build(); + public static final Customer FIONA = new CustomerBuilder(FIONA_ID).withName("Fiona Kunz").withPhone("9482427") + .withEmail("lydia@example.com").withAddress("little tokyo").build(); + public static final Customer GEORGE = new CustomerBuilder(GEORGE_ID).withName("George Best").withPhone("9482442") + .withEmail("anna@example.com").withAddress("4th street").build(); + + // Manually added + public static final Customer HOON = new CustomerBuilder(HOON_ID).withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").build(); + public static final Customer IDA = new CustomerBuilder(IDA_ID).withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").build(); + + // Manually added - Customer's details found in {@code CommandTestUtil} + public static final Customer AMY = + new CustomerBuilder(AMY_ID).withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); + public static final Customer BOB = + new CustomerBuilder(BOB_ID).withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) + .build(); + + public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER + + private TypicalCustomers() {} // prevents instantiation + + /** + * Returns an {@code InventorySystem} with all the typical persons. + */ + public static InventorySystem getTypicalInventorySystem() { + InventorySystem ab = new InventorySystem(); + for (Customer customer : getTypicalPersons()) { + ab.addPerson(customer); + } + return ab; + } + + public static List getTypicalPersons() { + return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); + } +} diff --git a/src/test/java/seedu/address/testutil/product/EditProductDescriptorBuilder.java b/src/test/java/seedu/address/testutil/product/EditProductDescriptorBuilder.java new file mode 100644 index 00000000000..02115a9f603 --- /dev/null +++ b/src/test/java/seedu/address/testutil/product/EditProductDescriptorBuilder.java @@ -0,0 +1,102 @@ +package seedu.address.testutil.product; + +import java.util.UUID; + +import seedu.address.logic.commands.product.EditProductCommand.EditProductDescriptor; +import seedu.address.model.product.CostPrice; +import seedu.address.model.product.Price; +import seedu.address.model.product.Product; +import seedu.address.model.product.ProductQuantity; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.QuantityThreshold; + +/** + * A utility class to help with building EditProductDescriptor objects. + */ +public class EditProductDescriptorBuilder { + + private EditProductDescriptor descriptor; + + public EditProductDescriptorBuilder() { + descriptor = new EditProductDescriptor(); + } + + public EditProductDescriptorBuilder(EditProductDescriptor descriptor) { + this.descriptor = new EditProductDescriptor(descriptor); + } + + /** + * Returns an {@code EditProductDescriptor} with fields containing {@code product}'s details + */ + public EditProductDescriptorBuilder(Product product) { + descriptor = new EditProductDescriptor(); + descriptor.setId(product.getId()); + descriptor.setDescription(product.getDescription()); + descriptor.setCostPrice(product.getCostPrice()); + descriptor.setPrice(product.getPrice()); + descriptor.setQuantity(product.getQuantity()); + descriptor.setSales(product.getMoney()); + descriptor.setThreshold(product.getThreshold()); + } + + /** + * Sets the {@code UUID} of the {@code EditProductDescriptor} that we are building. + */ + public EditProductDescriptorBuilder withId(UUID id) { + descriptor.setId(id); + return this; + } + + /** + * Sets the {@code Description} of the {@code EditProductDescriptor} that we are building. + */ + public EditProductDescriptorBuilder withDescription(String name) { + descriptor.setDescription(new Description(name)); + return this; + } + + /** + * Sets the {@code Price} of the {@code EditProductDescriptor} that we are building. + */ + public EditProductDescriptorBuilder withCostPrice(String costPrice) { + descriptor.setCostPrice(new CostPrice(costPrice)); + return this; + } + + /** + * Sets the {@code Price} of the {@code EditProductDescriptor} that we are building. + */ + public EditProductDescriptorBuilder withPrice(String price) { + descriptor.setPrice(new Price(price)); + return this; + } + + /** + * Sets the {@code Quantity} of the {@code EditProductDescriptor} that we are building. + */ + public EditProductDescriptorBuilder withQuantity(String quantity) { + descriptor.setQuantity(new ProductQuantity(quantity)); + return this; + } + + /** + * Sets the {@code Sales} of the {@code EditProductDescriptor} that we are building. + */ + public EditProductDescriptorBuilder withSales(String sales) { + descriptor.setSales(new Money(sales)); + return this; + } + + /** + * Sets the {@code QuantityThreshold} of the {@code EditProductDescriptor} that we are building. + */ + public EditProductDescriptorBuilder withThreshold(String threshold) { + descriptor.setThreshold(new QuantityThreshold(threshold)); + return this; + } + + public EditProductDescriptor build() { + return descriptor; + } +} diff --git a/src/test/java/seedu/address/testutil/product/ProductBuilder.java b/src/test/java/seedu/address/testutil/product/ProductBuilder.java new file mode 100644 index 00000000000..dfe08e3e830 --- /dev/null +++ b/src/test/java/seedu/address/testutil/product/ProductBuilder.java @@ -0,0 +1,122 @@ +package seedu.address.testutil.product; + +import java.util.UUID; + +import seedu.address.model.product.CostPrice; +import seedu.address.model.product.Price; +import seedu.address.model.product.Product; +import seedu.address.model.product.ProductQuantity; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; +import seedu.address.model.util.QuantityThreshold; + +/** + * A utility class to help with building Customer objects. + */ +public class ProductBuilder { + + public static final String DEFAULT_DESCRIPTION = "Abacus"; + public static final String DEFAULT_COSTPRICE = "1"; + public static final String DEFAULT_PRICE = "11"; + public static final String DEFAULT_QUANTITY = "11"; + public static final String DEFAULT_SALES = "10000"; + + private UUID id; + private Description description; + private CostPrice costPrice; + private Price price; + private Money money; + private Quantity quantity; + private QuantityThreshold threshold; + + public ProductBuilder(String id) { + this.id = UUID.fromString(id); + description = new Description(DEFAULT_DESCRIPTION); + costPrice = new CostPrice(DEFAULT_COSTPRICE); + price = new Price(DEFAULT_PRICE); + quantity = new ProductQuantity(DEFAULT_QUANTITY); + money = new Money(DEFAULT_SALES); + int calculatedThreshold = Integer.parseInt(DEFAULT_QUANTITY) / 5; + threshold = new QuantityThreshold(String.valueOf(calculatedThreshold)); + } + + /** + * Initializes the ProductBuilder with the data of {@code productToCopy}. + */ + public ProductBuilder(Product productToCopy) { + id = productToCopy.getId(); + description = productToCopy.getDescription(); + costPrice = productToCopy.getCostPrice(); + price = productToCopy.getPrice(); + quantity = productToCopy.getQuantity(); + money = productToCopy.getMoney(); + threshold = productToCopy.getThreshold(); + } + + /** + * Sets the {@code UUID} of the {@code Product} that we are building. + */ + public ProductBuilder withId(String id) { + this.id = UUID.fromString(id); + return this; + } + + /** + * Sets the {@code Description} of the {@code Product} that we are building. + */ + public ProductBuilder withDescription(String description) { + this.description = new Description(description); + return this; + } + + /** + * Sets the {@code CostPrice} of the {@code Product} that we are building. + */ + public ProductBuilder withCostPrice(String costPrice) { + this.costPrice = new CostPrice(costPrice); + return this; + } + + /** + * Sets the {@code Price} of the {@code Product} that we are building. + */ + public ProductBuilder withPrice(String price) { + this.price = new Price(price); + return this; + } + + /** + * Sets the {@code Quantity} of the {@code Product} that we are building. + */ + public ProductBuilder withQuantity(String quantity) { + this.quantity = new ProductQuantity(quantity); + return this; + } + + /** + * Sets the {@code Money} of the {@code Product} that we are building. + */ + public ProductBuilder withMoney(String money) { + this.money = new Money(money); + return this; + } + + /** + * Sets te {@code QuantityThreshold} of the {@code Product} that we are building. + */ + public ProductBuilder withThreshold(String threshold) { + this.threshold = new QuantityThreshold(threshold); + return this; + } + + /** + * Returns a product with the given attributes. + */ + public Product build() { + return new Product(id, description, costPrice, + price, quantity, money, threshold, 1); + } + +} + diff --git a/src/test/java/seedu/address/testutil/product/ProductUtil.java b/src/test/java/seedu/address/testutil/product/ProductUtil.java new file mode 100644 index 00000000000..efe8c309c6d --- /dev/null +++ b/src/test/java/seedu/address/testutil/product/ProductUtil.java @@ -0,0 +1,53 @@ +package seedu.address.testutil.product; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_COSTPRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_QUANTITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SALES; + +import seedu.address.logic.commands.product.AddProductCommand; +import seedu.address.logic.commands.product.EditProductCommand; +import seedu.address.model.product.Product; + +/** + * A utility class for Product. + */ +public class ProductUtil { + + /** + * Returns an add command string for adding the {@code product}. + */ + public static String getAddCommand(Product product) { + return AddProductCommand.COMMAND_WORD + " " + getProductDetails(product); + } + + /** + * Returns the part of command string for the given {@code product}'s details. + */ + public static String getProductDetails(Product product) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_DESCRIPTION + product.getDescription().value + " "); + sb.append(PREFIX_COSTPRICE + product.getCostPrice().value + " "); + sb.append(PREFIX_PRICE + product.getPrice().value + " "); + sb.append(PREFIX_QUANTITY + String.valueOf(product.getQuantity().getValue()) + " "); + sb.append(PREFIX_SALES + String.valueOf(product.getMoney().value) + " "); + return sb.toString(); + } + + /** + * Returns the part of command string for the given {@code EditProductDescriptor}'s details. + */ + public static String getEditProductDescriptorDetails(EditProductCommand.EditProductDescriptor descriptor) { + StringBuilder sb = new StringBuilder(); + descriptor.getDescription().ifPresent(description + -> sb.append(PREFIX_DESCRIPTION).append(description.value).append(" ")); + descriptor.getCostPrice().ifPresent(costPrice + -> sb.append(PREFIX_COSTPRICE).append(costPrice.value).append(" ")); + descriptor.getPrice().ifPresent(price -> sb.append(PREFIX_PRICE).append(price.value).append(" ")); + descriptor.getQuantity().ifPresent(quantity + -> sb.append(PREFIX_QUANTITY).append(quantity.getValue()).append(" ")); + descriptor.getMoney().ifPresent(sales -> sb.append(PREFIX_SALES).append(sales.value).append(" ")); + return sb.toString(); + } +} diff --git a/src/test/java/seedu/address/testutil/product/TypicalProducts.java b/src/test/java/seedu/address/testutil/product/TypicalProducts.java new file mode 100644 index 00000000000..c02a861b858 --- /dev/null +++ b/src/test/java/seedu/address/testutil/product/TypicalProducts.java @@ -0,0 +1,99 @@ +package seedu.address.testutil.product; + +import static seedu.address.logic.commands.CommandTestUtil.VALID_COSTPRICE_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_COSTPRICE_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DESCRIPTION_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PRICE_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PRICE_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_QUANTITY_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_QUANTITY_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SALES_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SALES_WATCH; +import static seedu.address.logic.commands.CommandTestUtil.VALID_THRESHOLD_BAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_THRESHOLD_WATCH; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.InventorySystem; +import seedu.address.model.product.Product; + +/** + * A utility class containing a list of {@code Customer} objects to be used in tests. + */ +public class TypicalProducts { + + public static final String ABACUS_ID = "56d39090-b1b6-4642-9e13-dfa8eae19289"; + public static final String BOOK_ID = "56d39091-b1b6-4642-9e13-dfa8eae19289"; + public static final String CAMERA_ID = "56d39092-b1b6-4642-9e13-dfa8eae19289"; + public static final String DISC_ID = "56d39093-b1b6-4642-9e13-dfa8eae19289"; + public static final String EGG_ID = "56d39094-b1b6-4642-9e13-dfa8eae19289"; + public static final String FAN_ID = "56d39095-b1b6-4642-9e13-dfa8eae19289"; + public static final String GAME_ID = "56d39096-b1b6-4642-9e13-dfa8eae19289"; + public static final String HAT_ID = "56d39097-b1b6-4642-9e13-dfa8eae19289"; + public static final String IPAD_ID = "56d39098-b1b6-4642-9e13-dfa8eae19289"; + public static final String WATCH_ID = "56d39099-b1b6-4642-9e13-dfa8eae19289"; + public static final String BAG_ID = "56d39100-b1b6-4642-9e13-dfa8eae19289"; + + public static final Product ABACUS = new ProductBuilder(ABACUS_ID).withDescription("Abacus") + .withPrice("12").withQuantity("1").withCostPrice("1").withThreshold("1") + .withMoney("100000").build(); + public static final Product BOOK = new ProductBuilder(BOOK_ID).withDescription("Beige Book") + .withPrice("311").withCostPrice("1").withThreshold("1") + .withQuantity("2").withMoney("98").build(); + public static final Product CAMERA = new ProductBuilder(CAMERA_ID).withDescription("Camera").withPrice("93") + .withQuantity("2").withMoney("123").withThreshold("1").withCostPrice("1").build(); + public static final Product DISC = new ProductBuilder(DISC_ID).withDescription("Disc").withPrice("83") + .withQuantity("3").withMoney("154").withThreshold("1").withCostPrice("1").build(); + public static final Product EGG = new ProductBuilder(EGG_ID).withDescription("Egg").withPrice("94") + .withQuantity("5").withMoney("543").withThreshold("1").withCostPrice("1").build(); + public static final Product FAN = new ProductBuilder(FAN_ID).withDescription("Fan").withPrice("94") + .withQuantity("4").withMoney("678").withThreshold("1").withCostPrice("1").build(); + public static final Product GAME = new ProductBuilder(GAME_ID).withDescription("Game").withPrice("92") + .withQuantity("8").withMoney("468").withThreshold("1").withCostPrice("1").build(); + + // Manually added + public static final Product HAT = new ProductBuilder(HAT_ID).withDescription("Hat").withPrice("84") + .withQuantity("22").withMoney("456").withThreshold("1").withCostPrice("1").build(); + public static final Product IPAD = new ProductBuilder(IPAD_ID).withDescription("Ipad").withPrice("81") + .withQuantity("37").withMoney("854").withThreshold("1").withCostPrice("1").build(); + + // Manually added - Customer's details found in {@code CommandTestUtil} + public static final Product WATCH = new ProductBuilder(WATCH_ID) + .withDescription(VALID_DESCRIPTION_WATCH) + .withCostPrice(VALID_COSTPRICE_WATCH) + .withPrice(VALID_PRICE_WATCH) + .withQuantity(VALID_QUANTITY_WATCH) + .withMoney(VALID_SALES_WATCH) + .withThreshold(VALID_THRESHOLD_WATCH) + .build(); + public static final Product BAG = new ProductBuilder(BAG_ID) + .withDescription(VALID_DESCRIPTION_BAG) + .withCostPrice(VALID_COSTPRICE_BAG) + .withPrice(VALID_PRICE_BAG) + .withQuantity(VALID_QUANTITY_BAG) + .withMoney(VALID_SALES_BAG) + .withThreshold(VALID_THRESHOLD_BAG) + .build(); + + public static final String KEYWORD_MATCHING_BEIGE = "Beige"; // A keyword that matches MEIER + + private TypicalProducts() {} // prevents instantiation + + /** + * Returns an {@code InventorySystem} with all the typical persons. + */ + public static InventorySystem getTypicalInventorySystem() { + InventorySystem ab = new InventorySystem(); + for (Product product : getTypicalProducts()) { + ab.addProduct(product); + } + return ab; + } + + public static List getTypicalProducts() { + return new ArrayList<>(Arrays.asList(ABACUS, BAG, BOOK, CAMERA, DISC, EGG, FAN, GAME)); + } +} diff --git a/src/test/java/seedu/address/testutil/transaction/DateTimeBuilder.java b/src/test/java/seedu/address/testutil/transaction/DateTimeBuilder.java new file mode 100644 index 00000000000..60bfe57e84b --- /dev/null +++ b/src/test/java/seedu/address/testutil/transaction/DateTimeBuilder.java @@ -0,0 +1,40 @@ +package seedu.address.testutil.transaction; + +import java.time.LocalDateTime; + +import seedu.address.model.transaction.DateTime; + +/** + * A utility class to help with building DateTime objects. + */ +public class DateTimeBuilder { + + public static final String DEFAULT_START_DATE = "2020-01-01 00:00"; + public static final String DEFAULT_END_DATE = "2020-12-12 23:59"; + public static final String DEFAULT_OTHER_START_DATE = "2020-02-02 00:01"; + public static final String DEFAULT_OTHER_END_DATE = "2020-11-30 23:58"; + + private LocalDateTime dateTime; + + public DateTimeBuilder() { + this.dateTime = LocalDateTime.parse(DEFAULT_START_DATE, DateTime.DATE_TIME_FORMAT); + } + + /** + * Initializes the DateTimeBuilder with the data of {@code startEndDate}. + */ + public DateTimeBuilder(String dateTime) { + this.dateTime = LocalDateTime.parse(dateTime, DateTime.DATE_TIME_FORMAT); + } + + /** + * Initializes the DateTimeBuilder with the data of {@code dateTimeToCopy}. + */ + public DateTimeBuilder(DateTime dateTimeToCopy) { + dateTime = dateTimeToCopy.value; + } + + public DateTime build() { + return new DateTime(dateTime); + } +} diff --git a/src/test/java/seedu/address/testutil/transaction/EditTransactionDescriptorBuilder.java b/src/test/java/seedu/address/testutil/transaction/EditTransactionDescriptorBuilder.java new file mode 100644 index 00000000000..3e44fd2d6c1 --- /dev/null +++ b/src/test/java/seedu/address/testutil/transaction/EditTransactionDescriptorBuilder.java @@ -0,0 +1,77 @@ +package seedu.address.testutil.transaction; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.transaction.EditTransactionCommand.EditTransactionDescriptor; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.TransactionQuantity; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; + +/** + * A utility class to help with building EditTransactionDescriptor objects. + */ +public class EditTransactionDescriptorBuilder { + + private EditTransactionDescriptor descriptor; + + public EditTransactionDescriptorBuilder() { + descriptor = new EditTransactionDescriptor(); + } + + public EditTransactionDescriptorBuilder(EditTransactionDescriptor descriptor) { + this.descriptor = new EditTransactionDescriptor(descriptor); + } + + /** + * Sets the customer Index of the {@code EditTransactionDescriptor} that we are building. + */ + public EditTransactionDescriptorBuilder withCustomerIndex(Index index) { + descriptor.setCustomerIndex(index); + return this; + } + + /** + * Sets the product index of the {@code EditTransactionDescriptor} that we are building. + */ + public EditTransactionDescriptorBuilder withProductIndex(Index index) { + descriptor.setProductIndex(index); + return this; + } + + /** + * Sets the {@code DateTime} of the {@code EditTransactionDescriptor} that we are building. + */ + public EditTransactionDescriptorBuilder withDateTime(String dateTime) { + descriptor.setDateTime(new DateTime(dateTime)); + return this; + } + + /** + * Sets the {@code Quantity} of the {@code EditTransactionDescriptor} that we are building. + */ + public EditTransactionDescriptorBuilder withQuantity(String quantity) { + descriptor.setQuantity(new TransactionQuantity(quantity)); + return this; + } + + /** + * Sets the {@code Money} of the {@code EditTransactionDescriptor} that we are building. + */ + public EditTransactionDescriptorBuilder withMoney(String money) { + descriptor.setMoney(new Money(money)); + return this; + } + + /** + * Sets the {@code Description} of the {@code EditTransactionDescriptor} that we are building. + */ + public EditTransactionDescriptorBuilder withDescription(String description) { + descriptor.setDescription(new Description(description)); + return this; + } + + public EditTransactionDescriptor build() { + return descriptor; + } +} + diff --git a/src/test/java/seedu/address/testutil/transaction/TransactionBuilder.java b/src/test/java/seedu/address/testutil/transaction/TransactionBuilder.java new file mode 100644 index 00000000000..e9c78d47959 --- /dev/null +++ b/src/test/java/seedu/address/testutil/transaction/TransactionBuilder.java @@ -0,0 +1,125 @@ +package seedu.address.testutil.transaction; + +import static seedu.address.testutil.customer.TypicalCustomers.ALICE; +import static seedu.address.testutil.product.TypicalProducts.BAG; + +import java.util.UUID; + +import seedu.address.model.customer.Customer; +import seedu.address.model.product.Product; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.Transaction; +import seedu.address.model.transaction.TransactionQuantity; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; + +/** + * A utility class to help with building Transaction objects. + */ +public class TransactionBuilder { + + public static final Customer DEFAULT_CUSTOMER = ALICE; + public static final Product DEFAULT_PRODUCT = BAG; + public static final String DEFAULT_DATETIME = "2020-01-01 10:00"; + public static final int DEFAULT_MONEY = 20; + public static final int DEFAULT_QUANTITY = 20; + public static final String DEFAULT_DESCRIPTION = "under promotion"; + + + // Identity fields + private Customer customer; + private Product product; + private UUID productId; + private UUID customerId; + private DateTime dateTime; + + // Data fields + private Money money; + private Quantity quantity; + private Description description; + + public TransactionBuilder() { + customer = DEFAULT_CUSTOMER; + product = DEFAULT_PRODUCT; + customerId = DEFAULT_CUSTOMER.getId(); + productId = DEFAULT_PRODUCT.getId(); + dateTime = new DateTime(DEFAULT_DATETIME); + money = new Money(DEFAULT_MONEY); + quantity = new TransactionQuantity(DEFAULT_QUANTITY); + description = new Description(DEFAULT_DESCRIPTION); + } + + /** + * Initializes the TransactionBuilder with the data of {@code transactionToCopy}. + */ + public TransactionBuilder(Transaction transactionToCopy) { + customer = transactionToCopy.getCustomer(); + product = transactionToCopy.getProduct(); + customerId = transactionToCopy.getCustomerId(); + productId = transactionToCopy.getProductId(); + dateTime = transactionToCopy.getDateTime(); + money = transactionToCopy.getMoney(); + quantity = transactionToCopy.getQuantity(); + description = transactionToCopy.getDescription(); + } + + /** + * Sets the {@code Customer} of the {@code Transaction} that we are building. + */ + public TransactionBuilder withCustomer(Customer customer) { + this.customer = customer; + this.customerId = customer.getId(); + return this; + } + + /** + * Sets the {@code Product} of the {@code Transaction} that we are building. + */ + public TransactionBuilder withProduct(Product product) { + this.product = product; + this.productId = product.getId(); + return this; + } + + + /** + * Sets the {@code DateTime} of the {@code Transaction} that we are building. + */ + public TransactionBuilder withDateTime(String dateTime) { + this.dateTime = new DateTime(dateTime); + return this; + } + + /** + * Sets the {@code Money} of the {@code Transaction} that we are building. + */ + public TransactionBuilder withMoney(int money) { + this.money = new Money(money); + return this; + } + + /** + * Sets the {@code Quantity} of the {@code Transaction} that we are building. + */ + public TransactionBuilder withQuantity(int quantity) { + this.quantity = new TransactionQuantity(quantity); + return this; + } + + /** + * Sets the {@code Description} of the {@code Transaction} that we are building. + */ + public TransactionBuilder withDescription(String description) { + this.description = new Description(description); + return this; + } + + /** + * Returns a transaction with the given attributes. + */ + public Transaction build() { + return new Transaction(customer, product, customerId, productId, dateTime, + quantity, money, description); + } +} diff --git a/src/test/java/seedu/address/testutil/transaction/TransactionFactoryBuilder.java b/src/test/java/seedu/address/testutil/transaction/TransactionFactoryBuilder.java new file mode 100644 index 00000000000..4e38e07f1cc --- /dev/null +++ b/src/test/java/seedu/address/testutil/transaction/TransactionFactoryBuilder.java @@ -0,0 +1,117 @@ +package seedu.address.testutil.transaction; + +import seedu.address.commons.core.index.Index; +import seedu.address.model.transaction.DateTime; +import seedu.address.model.transaction.TransactionFactory; +import seedu.address.model.transaction.TransactionQuantity; +import seedu.address.model.util.Description; +import seedu.address.model.util.Money; +import seedu.address.model.util.Quantity; + +/** + * A utility class to help with building TransactionFactory objects. + */ +public class TransactionFactoryBuilder { + + public static final Index DEFAULT_CUSTOMER_INDEX = Index.fromOneBased(1); + public static final Index DEFAULT_PRODUCT_INDEX = Index.fromOneBased(1); + public static final String DEFAULT_DATETIME = "2020-01-01 10:00"; + public static final int DEFAULT_MONEY = 1; + public static final int DEFAULT_QUANTITY = 1; + public static final String DEFAULT_DESCRIPTION = "under promotion"; + + private Index customerIndex; + private Index productIndex; + private DateTime dateTime; + private Quantity quantity; + private Money money; + private Description description; + + public TransactionFactoryBuilder() { + customerIndex = DEFAULT_CUSTOMER_INDEX; + productIndex = DEFAULT_PRODUCT_INDEX; + dateTime = new DateTime(DEFAULT_DATETIME); + money = new Money(DEFAULT_MONEY); + quantity = new TransactionQuantity(DEFAULT_QUANTITY); + description = new Description(DEFAULT_DESCRIPTION); + } + + /** + * Initializes the TransactionFactoryBuilder with the data of {@code transactionFactoryToCopy}. + */ + public TransactionFactoryBuilder(TransactionFactory transactionFactoryToCopy) { + customerIndex = transactionFactoryToCopy.getCustomerIndex(); + productIndex = transactionFactoryToCopy.getProductIndex(); + dateTime = transactionFactoryToCopy.getDateTime(); + money = transactionFactoryToCopy.getMoney(); + quantity = transactionFactoryToCopy.getQuantity(); + description = transactionFactoryToCopy.getDescription(); + } + + /** + * Sets the {@code customerIndex} of the {@code TransactionFactory} that we are building. + */ + public TransactionFactoryBuilder withCustomerIndex(Index customerIndex) { + this.customerIndex = customerIndex; + return this; + } + + /** + * Sets the {@code productIndex} of the {@code TransactionFactory} that we are building. + */ + public TransactionFactoryBuilder withProductIndex(Index productIndex) { + this.productIndex = productIndex; + return this; + } + + + /** + * Sets the {@code DateTime} of the {@code TransactionFactory} that we are building. + */ + public TransactionFactoryBuilder withDateTime(String dateTime) { + this.dateTime = new DateTime(dateTime); + return this; + } + + /** + * Sets the {@code Money} of the {@code TransactionFactory} that we are building. + */ + public TransactionFactoryBuilder withMoney(int money) { + this.money = new Money(money); + return this; + } + + /** + * Sets the {@code Quantity} of the {@code Transaction} that we are building. + */ + public TransactionFactoryBuilder withQuantity(int quantity) { + this.quantity = new TransactionQuantity(quantity); + return this; + } + + /** + * Sets the {@code Description} of the {@code Transaction} that we are building. + */ + public TransactionFactoryBuilder withDescription(String description) { + this.description = new Description(description); + return this; + } + + /** + * Sets the {@code Description} of the {@code Transaction} to N/A. + */ + public TransactionFactoryBuilder withDescription() { + description = new Description(Description.DEFAULT_VALUE); + return this; + } + + /** + * Returns a transaction with the given attributes. + */ + public TransactionFactory build() { + return new TransactionFactory(customerIndex, productIndex, dateTime, + quantity, money, description); + } + +} + diff --git a/src/test/java/seedu/address/testutil/transaction/TypicalDateTimes.java b/src/test/java/seedu/address/testutil/transaction/TypicalDateTimes.java new file mode 100644 index 00000000000..658fcd3d350 --- /dev/null +++ b/src/test/java/seedu/address/testutil/transaction/TypicalDateTimes.java @@ -0,0 +1,13 @@ +package seedu.address.testutil.transaction; + +import seedu.address.model.transaction.DateTime; + +/** + * A utility class containing a list of {@code DateTime} objects to be used in tests. + */ +public class TypicalDateTimes { + public static final DateTime MARCH_FIRST_2020_10AM = new DateTimeBuilder("2020-03-01 10:00").build(); + public static final DateTime MARCH_FIRST_2020_5PM = new DateTimeBuilder("2020-03-01 17:00").build(); + public static final DateTime MARCH_SECOND_2020_5PM = new DateTimeBuilder("2020-03-02 17:00").build(); + public static final DateTime MARCH_THIRD_2020_10AM = new DateTimeBuilder("2020-03-03 10:00").build(); +} diff --git a/src/test/java/seedu/address/testutil/transaction/TypicalTransactionFactories.java b/src/test/java/seedu/address/testutil/transaction/TypicalTransactionFactories.java new file mode 100644 index 00000000000..3b0deab5a09 --- /dev/null +++ b/src/test/java/seedu/address/testutil/transaction/TypicalTransactionFactories.java @@ -0,0 +1,20 @@ +package seedu.address.testutil.transaction; + +import seedu.address.commons.core.index.Index; +import seedu.address.model.transaction.TransactionFactory; + +/** + * A utility class containing a list of {@code TransactionFactory} objects to be used in tests. + */ +public class TypicalTransactionFactories { + + public static final TransactionFactory ONE_ONE_MARCH_FIRST_TWENTY_ONE = new TransactionFactoryBuilder() + .withCustomerIndex(Index.fromOneBased(1)).withProductIndex(Index.fromOneBased(1)) + .withDateTime("2020-03-01 10:00").withMoney(20).withQuantity(1).withDescription("under discount").build(); + public static final TransactionFactory ONE_ONE_MARCH_FIRST_TWENTY_TWO = new TransactionFactoryBuilder() + .withCustomerIndex(Index.fromOneBased(1)).withProductIndex(Index.fromOneBased(1)) + .withDateTime("2020-03-01 10:00").withMoney(20).withQuantity(2).withDescription("promotion").build(); + public static final TransactionFactory ONE_TWO_MARCH_FIRST_TWENTY_ONE = new TransactionFactoryBuilder() + .withCustomerIndex(Index.fromOneBased(1)).withProductIndex(Index.fromOneBased(2)) + .withDateTime("2020-03-01 10:00").withMoney(20).withQuantity(1).withDescription("normal price").build(); +} diff --git a/src/test/java/seedu/address/testutil/transaction/TypicalTransactions.java b/src/test/java/seedu/address/testutil/transaction/TypicalTransactions.java new file mode 100644 index 00000000000..3cca8f2e9ca --- /dev/null +++ b/src/test/java/seedu/address/testutil/transaction/TypicalTransactions.java @@ -0,0 +1,33 @@ +package seedu.address.testutil.transaction; + +import static seedu.address.testutil.customer.TypicalCustomers.ALICE; +import static seedu.address.testutil.customer.TypicalCustomers.BENSON; +import static seedu.address.testutil.product.TypicalProducts.ABACUS; +import static seedu.address.testutil.product.TypicalProducts.BAG; +import static seedu.address.testutil.product.TypicalProducts.BOOK; + +import seedu.address.model.transaction.Transaction; + +/** + * A utility class containing a list of {@code Transaction} objects to be used in tests. + */ +public class TypicalTransactions { + public static final Transaction ALICE_BUY_ONE_BAG_MARCH_FIRST = new TransactionBuilder().withCustomer(ALICE) + .withProduct(BAG).withDateTime("2020-03-01 10:00").withMoney(20).withQuantity(1) + .withDescription("promotion").build(); + public static final Transaction ALICE_BUY_TWO_BAG_MARCH_FIRST = new TransactionBuilder().withCustomer(ALICE) + .withProduct(BAG).withDateTime("2020-03-01 10:00").withMoney(20).withQuantity(2) + .withDescription("promotion").build(); + public static final Transaction ALICE_BUY_ONE_BOOK_MARCH_FIRST = new TransactionBuilder().withCustomer(ALICE) + .withProduct(BOOK).withDateTime("2020-03-01 10:00").withMoney(20).withQuantity(1) + .withDescription("normal price").build(); + public static final Transaction ALICE_BUY_ONE_BAG_MARCH_SECOND = new TransactionBuilder().withCustomer(ALICE) + .withProduct(BAG).withDateTime("2020-03-02 10:00").withMoney(20).withQuantity(1) + .withDescription("promotion").build(); + public static final Transaction BENSON_BUY_ONE_ABACUS_MARCH_FIRST = new TransactionBuilder().withCustomer(BENSON) + .withProduct(ABACUS).withDateTime("2020-03-01 10:00").withMoney(20).withQuantity(1) + .withDescription("normal price").build(); + public static final Transaction BENSON_BUY_ONE_BAG_MARCH_FIRST = new TransactionBuilder().withCustomer(BENSON) + .withProduct(BAG).withDateTime("2020-03-01 10:00").withMoney(20).withQuantity(1) + .withDescription("promotion").build(); +}