diff --git a/.gitignore b/.gitignore index 823d175eb670..de27f8bd4840 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ lib/* *.log *.log.* *.csv -config.json +/config.json src/test/data/sandbox/ preferences.json .DS_Store @@ -18,3 +18,7 @@ classes/ /bin/ src/main/resources/docs/ out/ + +# VSCode +.project +.classpath diff --git a/README.adoc b/README.adoc index 450054624f48..385fcdd9bb06 100644 --- a/README.adoc +++ b/README.adoc @@ -1,10 +1,9 @@ -= Address Book (Level 4) += FoodZoom ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] +https://travis-ci.org/CS2103-AY1819S1-T16-3/main[image:https://travis-ci.org/CS2103-AY1819S1-T16-3/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/juxd/main/branch/master[image:https://ci.appveyor.com/api/projects/status/bv2mp10bqjo93700/branch/master?svg=true[Build status]] +https://coveralls.io/github/CS2103-AY1819S1-T16-3/main?branch=master[image:https://coveralls.io/repos/github/CS2103-AY1819S1-T16-3/main/badge.svg?branch=master[Coverage Status]] https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] ifdef::env-github[] @@ -15,19 +14,14 @@ ifndef::env-github[] image::images/Ui.png[width="600"] 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. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +Welcome to FoodZoom! FoodZoom is a desktop food delivery management application designed for food businesses to streamline their delivery process. FoodZoom is responsive and quick, allowing managers to plan routes for order delivery and dispatch them to delivery men using a Command Line Interface (CLI). The delivery men can also use this application to view their delivery routes and increase their efficiency. + +Make your food delivery system more seamless today with FoodZoom! == Site Map * <> * <> -* <> * <> * <> @@ -36,5 +30,7 @@ endif::[] * 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://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5] +* AddressBook-Level4 project is created by SE-EDU initiative at https://github.com/se-edu/ +* Icons used in this application are made by Freepik from https://www.flaticon.com == Licence : link:LICENSE[MIT] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..d2643eac8a2d --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,32 @@ +{ + "ignoreGlobList": ["about-us/**", "**index.html"], + "ignoreCommitList": [], + "authors": + [ + { + "githubId": "kohchihao", + "displayName": "Koh Chi Hao", + "authorNames": ["Marcus Koh"] + }, + { + "githubId": "jinyingtan", + "displayName": "Tan Jin Ying", + "authorNames": ["Jin Ying"] + }, + { + "githubId": "monmanuela", + "displayName": "Monika Manuela Hengki", + "authorNames": ["Monika", "Monika Manuela Hengki"] + }, + { + "githubId": "juxd", + "displayName": "Julius Sander", + "authorNames": ["Julius S"] + }, + { + "githubId": "rrtheonlyone", + "displayName": "Rahul Rajesh", + "authorNames": ["Rahul Rajesh"] + } + ] +} diff --git a/build.gradle b/build.gradle index f8e614f8b49b..450ca8cae6a5 100644 --- a/build.gradle +++ b/build.gradle @@ -61,7 +61,7 @@ dependencies { implementation group: 'org.controlsfx', name: 'controlsfx', version: '8.40.11' 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' - implementation group: 'com.google.guava', name: 'guava', version: '19.0' + implementation group: 'com.google.guava', name: 'guava', version: '26.0-jre' implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.2.8' implementation group: 'com.sun.xml.bind', name: 'jaxb-impl', version: '2.3.0' implementation group: 'com.sun.xml.bind', name: 'jaxb-core', version: '2.3.0' diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index e647ed1e715a..a8744374a25a 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,48 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 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.}_ + -{empty} + +FoodZoom was developed by Team https://github.com/CS2103-AY1819S1-T16-3[T16-3] team. + + 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]] [<>] +=== Rahul +image::rrtheonlyone.png[width="150", align="left"] +{empty}[https://github.com/rrtheonlyone[github]] [<>] -Role: Project Advisor +Roles: Integration, code quality ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Julius Sander +image::juxd.png[width="150", align="left"] +{empty}[https://github.com/juxd[github]] [<>] -Role: Team Lead + -Responsibilities: UI +Roles: Code quality and testing + ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Monika Manuela Hengki +image::monmanuela.png[width="150", align="left"] +{empty}[http://github.com/monmanuela[github]] [<>] -Role: Developer + -Responsibilities: Data +Roles: Team lead, UI + ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Koh Chi Hao +image::kohchihao.png[width="150", align="left"] +{empty}[https://github.com/kohchihao[github]] [<>] -Role: Developer + -Responsibilities: Dev Ops + Threading +Roles: Scheduling and tracking deliverables and deadlines + ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Tan Jin Ying +image::jinyingtan.png[width="150", align="left"] +{empty}[https://github.com/jinyingtan[github]] [<>] -Role: Developer + -Responsibilities: UI +Role: Documentation, UI + ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 5de5363abffd..454edc084162 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,7 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/CS2103-AY1819S1-T16-3/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 `e0176905 [at] comp.nus.edu.sg` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 817ec81d7832..b196d8af9af4 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += FoodZoom - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -13,9 +13,9 @@ ifdef::env-github[] :warning-caption: :warning: :experimental: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103-AY1819S1-T16-3/main -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Team T16-3`      Since: `Oct 2018`      Licence: `MIT` == Setting up @@ -47,10 +47,6 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu . Click `OK` to accept the default settings . Open a console and run the command `gradlew processResources` (Mac/Linux: `./gradlew processResources`). It should finish with the `BUILD SUCCESSFUL` message. + This will generate all resources required by the application and tests. -. Open link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson.java`] and link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow.java`] and check for any code errors -.. Due to an ongoing https://youtrack.jetbrains.com/issue/IDEA-189060[issue] with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully -.. To resolve this, place your cursor over any of the code section highlighted in red. Press kbd:[ALT + ENTER], and select `Add '--add-modules=...' to module compiler options` for each error -. Repeat this for the test folder as well (e.g. check link:{repoURL}/src/test/java/seedu/address/commons/util/XmlUtilTest.java[`XmlUtilTest.java`] and link:{repoURL}/src/test/java/seedu/address/ui/HelpWindowTest.java[`HelpWindowTest.java`] for code errors, and if so, resolve it the same way) === Verifying the setup @@ -105,7 +101,6 @@ When you are ready to start coding, == Design -[[Design-Architecture]] === Architecture .Architecture Diagram @@ -146,33 +141,33 @@ image::LogicClassDiagram.png[width="800"] [discrete] ==== Events-Driven nature of the design -The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `delete 1`. +The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `/order delete 1`. -.Component interactions for `delete 1` command (part 1) -image::SDforDeletePerson.png[width="800"] +.Component interactions for `/order delete 1` command (part 1) +image::SDforDeleteOrder.png[width="800"] [NOTE] -Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. +Note how the `Model` simply raises a `OrderBookChangedEvent` when the Order Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. -.Component interactions for `delete 1` command (part 2) -image::SDforDeletePersonEventHandling.png[width="800"] +.Component interactions for `/order delete 1` command (part 2) +image::SDforDeleteOrderEventHandling.png[width="800"] [NOTE] Note how the event is propagated through the `EventsCenter` to the `Storage` and `UI` without `Model` having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components. The sections below give more details of each component. -[[Design-Ui]] === UI component .Structure of the UI Component -image::UiClassDiagram.png[width="800"] +image::UiClassDiagram1.png[width="800"] *API* : link:{repoURL}/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`, `BrowserPanel` 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`, `OrderListPanel`, +`StatusBarFooter`, `Dashboard` 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`] @@ -182,7 +177,6 @@ The `UI` component, * Binds itself to some data in the `Model` so that the UI can auto-update when data in the `Model` change. * Responds to events raised from various parts of the App and updates the UI accordingly. -[[Design-Logic]] === Logic component [[fig-LogicClassDiagram]] @@ -192,17 +186,17 @@ image::LogicClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `OrderBookParser` class to parse the user command. +. `OrderBookParser` further creates either a `OrderCommandParser` or `DeliverymanCommandParser` to parse order or deliveryman command respectively. Other commands that is neither order or deliveryman skip this creation of the extra Parser. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. +. The command execution can affect the `Model` (e.g. adding an order) and/or raise events. . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. -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("/order delete 1")` API call. -.Interactions Inside the Logic Component for the `delete 1` Command -image::DeletePersonSdForLogic.png[width="800"] +.Interactions Inside the Logic Component for the `/order delete 1` Command +image::DeleteOrderSdForLogic.png[width="800"] -[[Design-Model]] === Model component .Structure of the Model Component @@ -213,18 +207,22 @@ image::ModelClassDiagram.png[width="800"] 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 Order data. +* stores the Users data. +* stores the Deliveryman data. +* stores a `UserSession` object that represents the user's login session. +* 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. + - + -image:ModelClassBetterOopDiagram.png[width="800"] - -[[Design-Storage]] +// tag::storagecomponent[] === Storage component +The following diagram shows how the Storage component is structured. + .Structure of the Storage Component image::StorageClassDiagram.png[width="800"] @@ -233,9 +231,37 @@ image::StorageClassDiagram.png[width="800"] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in xml format and read it back. +* can save the FoodZoom data in xml format and read it back. +** FoodZoom allows deliverymen and users to be stored separately to prevent +duplication of data while retaining object associations. +* can save the Users' data in xml format and read it back. + +The following diagram depicts the sequence diagram of how `StorageManager` saves changes every time a change is made +in FoodZoom. + +.Interactions inside the Storage component for whenever `ModelManager.saveFoodZoom(orderBook, deliverymenList)` is called. +image::StorageSequenceDiagram.png[width="800"] + +==== Design considerations + +===== Aspect: Implementation of file storage structure for FoodZoom +This concerns the implementation of how application data is stored in a reloadable file. + +* **Alternative 1**: Use two separate files for the storage of FoodZoom data +** Pros: +*** This solution presents a better separation of concerns between deliverymen and orders. This means that corruption of one +file would not affect the other. +** Cons: +*** It is more difficult to manage JXML marshalling from two separate files. + +* **Alternative 2 (current choice):** Use a single file to the storage of `deliverymen` and `orders` +** Pros: +*** Coupling of `deliverymen` and `orders` allows more cohesion, as information like `id` for a deliveryman's orders would +be unusable without knowing the orders. +** Cons: +*** Corruption of one file would mean corruption of all application data. +// end::storagecomponent[] -[[Design-Commons]] === Common classes Classes used by multiple components are in the `seedu.addressbook.commons` package. @@ -244,92 +270,552 @@ 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[] -=== Undo/Redo feature +// tag::loginfeature[] +=== Login Command ==== Current Implementation -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: +The `/login` command allows the `Manager` to login into the FoodZoom application and use authenticated commands like +`/order add`, `/order edit`, `/order list`, ... .Basically all the `/order` commands and `/deliveryman` commands. The +only commands that can be used without logging in are `/signup`, `/login`, `/help` and `/history`. The format of this + command is `/login u/[USERNAME] pw/[PASSWORD]`. -* `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. +In our current implementation, the `/login` command inherits from `Command` class. 2 components, `Logic` and `Model` +are involved in the execution of this command. The `Logic` class is responsible for parsing the user input and the +`Model` class deals with storing the `UserSession` and checking if the user is logged in or not. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +Below is a sequence diagram that illustrates how these 2 components interact when the `/login` command is executed: -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +image::LoginCommandSequenceDiagram1.png[width="800"] +Figure 3.1.1.1 Sequence diagram to illustrate `Logic` component interactions for `/login`. -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. +image::LoginCommandSequenceDiagram2.png[width="800"] +Figure 3.1.1.2 Sequence diagram to illustrate `Logic` and `Model` component interactions for `/login`. -image::UndoRedoStartingStateListDiagram.png[width="800"] +As shown above, execution of the `/login` command comprises of the following steps: -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. +1. `LogicManager` invokes the `parseCommand` method of `OrderBookParser`, taking in user inputs as arguments. +2. During the `parseCommand` method call, an instance of `LoginCommandParser` will be created with the keyword +`/login` is matched. `LoginCommandParser` then extracts the remaining user inputs and a `LoginCommand` instance will +be returned provided that the user's input is correct. +3. `LoginCommand` then invokes `isRegisteredUser` method which belongs to `ModelManager` class. The `ModelManager` +will the invoke `IsRegisteredUser` method in `UsersList` to check if the user is in the list of users. +4. After checking if the user is in the list, if it returns `true` then `storeUserInSession` method will be invoked +to store the `User` object inside `UserSession`. The `execute` method will then return a `CommandResult` with success + login message. +5. If the user is not in the list, `execute` method will just return a `CommandResult` with failure to login message. -image::UndoRedoNewCommand1StateListDiagram.png[width="800"] +==== Design Considerations -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`. +===== Aspect: How `/login` executes -image::UndoRedoNewCommand2StateListDiagram.png[width="800"] +* **Alternative 1 (current choice):** Check if user is in users list. +** Pros: Easy to implement. +** Cons: Not necessary to implement users list because there will only be 1 user. -[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`. +* **Alternative 2:** Automatic login even after application is opened more than once. +** Pros: User do not need to login multiple times after application is re-opened. +** Cons: Difficult to implement because it requires caching user details locally. -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. +===== Aspect: Data Structure to support `/login` command -image::UndoRedoExecuteUndoStateListDiagram.png[width="800"] +* **Alternative 1 (current choice):** Create a unique user list to store all the user and create a `users.xml` to +store locally. +** Pros: Easy to implement and you can make sure that there is only unique users. +** Cons: Not necessary because there would only be 1 user using the application. -[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. +* **Alternative 2:** Store user session inside `User` class +** Pros: Easy to handle login session. +** Cons: Would break the SRP (Single Responsibility Principle) because `User` class should only be about the user and + nothing to do with login at all. -The following sequence diagram shows how the undo operation works: +===== Aspect: Checking if user is logged in -image::UndoRedoSequenceDiagram.png[width="800"] +* **Alternative 1 (current choice):** Check if user is logged in after command is parsed +** Pros: It is easy to implement and it doesn't violate the SRP because it wouldn't make sense to check if the user +is logged in inside a method that is supposed to be parsing the user input. +** Cons: User is able to type authenticated commands like `/order add` and receive a parse exception which is the +incorrect behavior. -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. +* **Alternative 2:** Check if user is logged in before command is parsed +** Pros: It is easy to implement. +** Cons: It breaks the SRP because `parseCommand` method should only be used to parse commands and not check for user + logged in status. +// end::loginfeature[] -[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. +// tag::dashboard[] +=== Dashboard -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. +==== Current Implementation -image::UndoRedoNewCommand3StateListDiagram.png[width="800"] +The dashboard for this application is the main screen that the user first sees when he or +she logs into the application. It looks like this: -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. +image::Ui.png[width="400"] +Figure 3.2.1.1 Main Screen showing the dashboard with the Map and Statistics Panel -image::UndoRedoNewCommand4StateListDiagram.png[width="800"] +The 2 main components in the dashboard are the: -The following activity diagram summarizes what happens when a user executes a new command: +.. Map showing location of pending orders +.. Statistics Panel giving insights for the data -image::UndoRedoActivityDiagram.png[width="650"] +Before delving into the details for these specific components, it is +important to consider some of the implementation details that are common to both. -==== Design Considerations +The dashboard dynamically updates itself when the order list has been changed. For example, +if `/order find n/john` is executed, the dashboard will also show orders that are specific +to john. + +This is done by using the observer pattern. A higher-level component, `Display.java` listens for +changes and renders the user interface for the dashboard screen. This component attaches +a listener to the Observable List of Orders. The following activity diagram summarize +what happens inside the `Display` Component when a user executes a new command: + +image::UIListenerActivityDiagram.png[width="500"] +Figure 3.2.1.1 Activity Diagram showing what happens inside the `Display` component when a user +executes a command + +The code snippet for the listener being attached is shown below: + +``` +this.orderList.addListener((ListChangeListener.Change change) -> { + while (change.next()) { + if (change.wasUpdated()) { + // handle order list being updated + .... + } else { + // handle removal or addition to order list + .... + } + } +}); +``` + +Through this, the `Display` component is able to control the flow of the dashboard. It is responsible for +rendering the Map and Statistics components along with passing in the data that +they need. The data to be passed in has to be processed into a specific format so that +they can be easily rendered. The next two subsections in 3.2 would elaborate on how this +data processing is done. + +==== Map Component + +The Map shows the locations of the pending orders. + +image::MapPanel.png[width="400"] +Figure 3.2.2.1 A closer look at the Map on the dashboard. + +The functionality that the map provides is as follows: + +.. It works offline and does not require internet. There is no reliance on any external service +like Google Maps +.. Placeholders are rendered based on the location of Order given +.. The size of the placeholder is indicative of the number of orders in a specific area. + +In order to work offline, the map makes use of the postal code that is given +with the order address to give a *reasonable* approximation of its location in Singapore. +The reason this can be done is because the first 2 digits of a Singapore postal code +refers to a specific district in Singapore. The demarcation diagram is show below: + +image::SingaporeDemarcation.png[width="500"] +Figure 3.2.1.2 Diagram to illustrate how the regions are split up in Singapore. Each of the +regions above corresponds to a specifc postal code tag + +For example, if the first 2 digits of the given postal code is "61", the +Order falls under district 22 (with reference to the image above). If the first 2 digits are +"47", the location is district 16. + +Based on this information in order to create an offline map, the components/data structures +used are as follows: + +**An image of a Map that lies on a grid (JavaFx `GridPane`)** + +This is the component that displays on the dashboard. This component creates and +updates the placeholders on the map based on the information that it is provided. The component +has no knowledge of changes to the list of orders. Instead, it exposes 2 methods, `initialise` +and `clear` that is used by the higher-order component to update the placeholders. +// end::dashboard[] + +The code snippet for the initialisation of the placeholders is shown below. Further details on +the data passed in is given below. +``` +public void initialise(HashMap orderMap) { + // for each postal code + for (String postalCodeKey : orderMap.keySet()) { + + if (POSTAL_CODE_CACHE.containsKey(postalCodeKey)) { + + int regionCode = POSTAL_CODE_CACHE.get(postalCodeKey); + Integer[] coordinates = DISTRICT_CACHE.get(regionCode); + + placeholderContainer = new ImageView(placeholder); + + int count = orderMap.get(postalCodeKey); + int increment = 16 + count * 2; + + placeholderContainer.setPreserveRatio(true); + placeholderContainer.setFitHeight(increment); + placeholderContainer.setFitWidth(increment); + + mapPane.add(placeholderContainer, coordinates[1], coordinates[0]); + } + } +} +``` +// tag::dashboard2[] +**A Hashtable (Java `HashMap`) that maps the first two digits of the postal +code to a specific coordinate on the grid** + +As mentioned above, the map is an image that lies on a grid. The placeholders are +put onto a specific coordinate specified on the grid. The Map component is thus given +a Hash Table of how many orders fall under a specific postal code region. An example data that it +could be given is shown below: + +[width="59%",cols="",options="header",] +|======================================================================= +|Postal Code Region |Count +| 61 | 5 +| 16 | 1 +| 32 | 2 +|======================================================================= + +Using this information, the `MapPanel` uses another lookup table to find out +which coordinate the placeholder should be put inside. For example, postal code 61, is +to be put inside grid {row: 6, column: 2}. The size of the placeholder is determined +by the `count` of orders that fall within a specific region. + +The job of the `Display` component is to process the list of Orders into the format shown +in the table above. It then calls specific method on the `MapPanel` component to allow +for placeholders to be shown on the map. This is how the map is able to show locations +of orders without relying on any internet connection. + + +==== Statistics Panel + +Another aspect for the dashboard is the statistics panel. + +image::StatisticsPanel.png[width="250"] +Figure 3.2.3.1 A closer look at the statistics panel in the dashboard + +The statistics panel has four key indicators: + +.. The total number of orders displayed +.. The percentage of orders that are still pending +.. The most common food item bought +.. The Order History for the last 7 days in which there was at least one order + +Similar to the `MapPanel`, there is a `StatisticsPanel` which updates these statistics based +on the information that it is provided from a higher-level component. It has similar methods +to the `MapPanel` that allow data to be initialized and cleared. + +The data that is passed in to the `StatisticsPanel` along with the techniques employed to +process the data is further elaborated on in the table below: + +[width="90%",cols="",options="header",] +|======================================================================= +|Statistic |Data Structure | Technique +| Total Orders | Variable | Count the orders in the list +| Pending Order Percentage | Variable | Count orders with status "PENDING" and divide with total orders +| Trending Food Item | HashTable | Use a lookup table to track the count of each food item in menu. +Each time there is an update ot the orders, run through the table and see which food item has the largest count value. +| Order Histogram | Balanced Binary Search Tree (Java `TreeMap`) | The dates from the Orders are put +into a binary search tree along with their count. The tree uses a custom comparator that allows for dates to be +compared with each other. This allows the 7 most recent dates with at least one order to be retrieved quickly. The +tree is updated accordingly when orders are added or removed. +|======================================================================= +// end::dashboard2[] + +// tag::dashboard3[] +==== Design Considerations (for both Map/Statistics) + +===== Aspect: Processing of data +* **Alternative 1 (Current Approach):** Let the UI component handle the +processing of data once the listener detects a change +** Pros: +- Separation of Concerns: the UI need not rely on other components to +update the statistics once a change has been detected. +** Cons: +- There is a lot of code within the UI component itself and this solution +is not very scalable. If more statistics are needed in the future, it will +be difficult to configure. + +* **Alternative 2 :** Create a Statistics Manager under Model + +Instead of doing all the processing of data in the UI component itself, +one consideration was to use a statistics manager. This +manager will take in the list of orders and return the required statistics needed. + +** Pros: +- This solution is more scalable. By having a separate class dedicated to managing the +statistics of orders, it is easier to add in much more +complex statistics for the application in future. +** Cons: +- There is more coupling. The UI has to constantly check with the +statistics manager once a change to the observable list is detected. +- The code becomes much more complex. There also may be a need to re-look at +how the entire listener pattern is established. + + +===== Aspect: Gathering Location Data +* **Alternative 1 (Current Approach):** Use a cache to map points based +on the first 2 digits of postal code +** Pros: +- The application would work offline. +- There is no reliance on an external API service for the application to +function. If the API service fails to function at any point of time, the +map may not work anymore. +** Cons: +- There are inaccuracies in the data that is displayed on the map. It is also +not very precise. e.g. You cannot zoom into the map and locate where +exactly the orders are. + +* **Alternative 2 :** Make use of an external API service like Google +Maps to render the location placeholders on the map + +** Pros: +- The location data would be much more accurate and precise. Locations +can be mapped exactly as they are. Relying on a powerful service could +mean that this map feature can even be extended to work globally (not +just Singapore). +** Cons: +- As mentioned above, there will now be a reliance on this service for this +application to work. This would also introduce more overhead to the code e.g +there is now a need to check for internet connection and render loaders so +that the user experience is not affected. +// end::dashboard3[] + +// tag::orderAdd[] +=== Add Order Feature + +==== Current Implementation +The `add` order command allows the `Manager` to create a new order and add it to the list of orders. +Adding a new order requires the customer's `name`, customer's `phone` number, customer's `address`, `food` and the `datetime`. + +The format of the `add` command is `/order add n/NAME p/PHONE a/ADDRESS f/FOOD dt/DATETIME`. -===== Aspect: How undo & redo executes +The following sequence shows the sequence when the `add` command is execute by the `Manager`: -* **Alternative 1 (current choice):** Saves the entire address book. +image::OrderAddSequenceDiagram.png[width="650"] +Figure 3.3.1.1 Sequence Diagram for order `find` command + +From the diagram above: + +. `LogicManager`'s execute is called when the `Manager` keys in `/order add` and it calls upon `parseCommand` of `OrderBookParser` to parse the command +. `OrderBookParser` will initialize `OrderCommandParser` and invoke the method `parse` to further parse `order` command +. `AddCommandParser` will be invoked and passed the parameters of the add command. +. If all the arguments of the `add` commands are valid, `AddCommand` will be returned to the `LogicManager` +. `LogicManger` will then calls the method `execute` method of `AddCommand` +. `AddCommand` will call `addOrder` passing `Order` as an argument to `Model` and after calls `commitOrderBook` method from `Model` +. A `CommandResult` will be returned at the end, + +==== Design Consideration +===== Aspect: Data Structure for the list of Orders +* **Alternative 1 (Current Approach):** Using a `UniqueOrderList` +** Pros: The comparison is not as rigid. You can create orders with similar names/address as long as it passes a less stringent criteria. +** Cons: You need additional overhead. You need to create an extra class and function to check. + +* **Alternative 2:** Using a `Set` +** Pros: Do not need an extra function. You can use use `equals` for comparison +** Cons: It is a strict check and as a result you will not be allowed to create orders with same address/same name etc. + +===== Aspect: Data Structure of `Order` +* **Alternative 1 (Current Approach):** All parameters in `Order` are abstracted out to have a class of its own. +** Pros: It adheres to the Single Responsibility Principles (SRP) and the Separation of Concerns (SoC) as each parameter checks whether it is valid +** Cons: Many different classes are created which increases the complexity of the code + +* **Alternative 2:** Store all parameters of `Order` as `String` ** 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. +** Cons: Violates SRP as one single class will need to check if the arguments are valid. +// end::orderAdd[] + +// tag::orderfindfeature[] +=== Find Order Feature + +==== Current Implementation +The order `find` command allows searching of orders in FoodZoom. It allows searching for orders with any order fields. + +The following sequence diagram shows the sequence flow from the `LogicManager` to the `ModelManager` when a user enter a `find` command: + +image::OrderFindSequenceDiagram.png[width="650"] +Figure 3.4.1.1 Sequence Diagram for order `find` command + +From the sequence diagram: + +. When `LogicManager` receive the `execute` command, it calls the `parseCommand` method in `OrderBookParser`. +. `OrderBookParser` will receive `/order` as the command and instantiate `OrderCommandParser` to further parse the command. +. `OrderCommandParser` receive `find` as the command and calls `FindCommandParser` to handle the fields for the find command. +. If at least one field is provided, `FindCommandParser` will call `parsePredicate` of `OrderPredicateUtil` to handle the given fields. +. `OrderPredicateUtil` will call its own `chainPredicate` to create the predicate for the relevant fields. +. If all fields give are valid, `FindCommand` will be created and return back to `LogicManager`. +. `LogicManager` will proceed to call execute of `FindCommand`. +. `FindCommand` will update the list in the `Model` and create a new `CommandResult` to be returned. + +The differentiation is handled by `OrderPredicateUtil` which checks for the fields provided. It throws `ParseException` if a invalid order field is specified or when any field is given without any value. + +`OrderPredicateUtil` will chain up the different fields and return a `Predicate` to narrow the scope +of the search. + +`Datetime` search can be performed in two ways. Below is a sequence diagram to show flow of how `datetime` search: + +image::OrderDatePredicate.png[width="650"] +Figure 3.4.1.2 Sequence Diagram for `datetime` search + +* If one `datetime` field is given, `OrderDatePredicate` creates a `OrderDateSinglePredicate` object and run its' `test` method. + +* If more than one `datetime` field is given, `OrderDatePredicate` creates a `OrderDateRangePredicate` object and run its' `test` method. + +==== Design Consideration + +===== Aspect: Implementation of `FindCommand` +* **Alternative 1 (current choice):** All different order field having its own test method. +** Pros: It adheres to the Single Responsibility Principle (SRP) and the Separation of Concern (SoC). Each predicate handles its own search values. +** Cons: More classes will be needed, which results in higher complexity of the code base. +* **Alternative 2:** One predicate class to handle all the predicates. +** Pros: Reduces the number of classes, which makes it for new developers to understand. +** Cons: It decreases the cohesion as one class will need to handle different predicates. + +===== Aspect: Implementation of `FindCommandParser` +* **Alternative 1 (current choice):** Further parsing of the predicates are passed to `OrderPredicateUtil` to handle +** Pros: It adheres to SRP as the handling of the predicates are passed on to `OrderPredicateUtil` class. +** Cons: Increase in the complexity of the code as more classes are needed, with more lines of code being written +* **Alternative 2:** `FindCommandParser` to handles the predicates. +** Pros: It makes it easy for new developers to understand the code as `FindCommandParser` handles all the parsing of the predicates +** Cons: It violates SRP. `FindCommandParser` has to break down the fields as well as check which fields are present. +// end::orderfindfeature[] + +// tag::deliverymanaddfeature[] +=== Add deliveryman feature + +==== Current Implementation + +The `add` deliveryman command allows the Manager to add deliveryman into FoodZoom. It ensures that the newly-added deliveryman +is not a duplicate of an existing deliveryman. + +The following diagram shows the sequence flow from the `LogicManager` to the `ModelManager` when a user enters a `/deliveryman add` +command: -===== Aspect: Data structure to support the undo/redo commands +image::DeliverymanAddCommandSequenceDiagram.png[width="650"] +Figure 3.5.1.1 Sequence Diagram for deliveryman `add` command -* **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[] +From Figure 3.5.1.1: -// tag::dataencryption[] -=== [Proposed] Data Encryption +1. When `LogicManager` receives the `execute` command from the App, it calls the `parseCommand` method in `OrderBookParser`. + +2. `OrderBookParser` will receive `/deliveryman` as the command and instantiate `DeliverymanCommandParser` to parse the rest of the command. + +3. If the arguments specified in the `add` command are valid, a `DeliverymanAddCommand` would be created and returned back +to the `LogicManager`. + +4. `LogicManager` would proceed to call `execute` of the `DeliverymanAddCommand`. + +5. A new deliveryman would be added and a successful `CommandResult` would be returned and displayed. + +FoodZoom's deliveryman is only stored with names currently, and can be search and listed +only with names. + +These names are checked against validations with the `Name` model, and this is a compulsory +field for a deliveryman to be added. If the provided command either does not have a name, or is an invalid `Name`, +a `ParseException` would be thrown. + +==== Design Considerations + +===== Implementation of `DeliverymanAddCommandParser` +This concerns the implementation and organization of a `DeliverymanAddCommand` + +* ** Alternative 1 (current choice):** `DeliverymanCommand` is parsed first, then as a `DeliverymanAddCommand`. +** Pros: Better modularization for all commands relating to `Deliveryman` +** Cons: More complicated file & package organization + +* ** Alternative 2: ** `DeliverymanAddCommand` is parsed straight from `OrderBookParser` +** Pros: Easier to manage file structure +** Cons: Poor modularization of related `Command` classes. + +===== Implementation of `DeliverymanAddCommand` +This concerns how deliverymen data is stored with relation to orders. + +* **Alternative 1 (current choice):** Deliverymen are stored in separate file +** Pros: Allows for greater flexibility in how deliverymen interact with other resources like order/routes - better separation of concerns. +** Cons: Harder to implement & maintain + +* **Alternative 2:** Deliverymen are stored in the same file as `Order` +** Pros: Easier to implement & maintain +** Cons: Deliveryman can only be accessed & treated as parts of an `Order` +// end::deliverymanaddfeature[] + +// tag::route[] +=== Create Route Feature [deprecated since v1.2.1] +This feature is deprecated as there was a design change regarding the relationship between delivery man and order. +Previously, route was the middleman between delivery men and orders. Now, delivery men and orders are directly related. +Route is removed as its existence will complicate implementations of other features without adding much benefit. + +==== Current Implementation +The `create` route command allows creation of routes in FoodZoom. +It allows creating routes based on the order ids. + +The following sequence diagram shows the sequence flow from the `LogicManager` to the `ModelManager` when a user enter a `/route create` command: + +image::CreateRouteCommandSequenceDiagram1.png[width=800] +Figure 3.6.1.1 Sequence diagram to illustrate `Logic` component interactions for route `create` command. + +image::CreateRouteCommandSequenceDiagram2.png[width=800] +Figure 3.6.1.2 Sequence diagram to illustrate `Logic` and `Model` component interactions for route `create` command. + +From the sequence diagram: + +. When `LogicManager` receive the `execute` command, it calls the `parseCommand` method in `OrderBookParser`. +. `OrderBookParser` will receive `/route` as the command and instantiate `RouteCommandParser` to further parse the command. +. If the arguments specified in the `create` command are valid, a `CreateRouteCommand` will be created and return back to the `LogicManger` +. `LogicManager` will proceed to call the `execute` command of `CreateRouteCommand` +. `CreateRouteCommand` will proceed to call the `getFilteredOrderList` method of `Model` +. Iterate through the `orderIds`, if valid, add the `Index` and the corresponding `Order` to the set of `Index` and `Order` respectively. +. Create a `Route` from the set of `Order` +. A new route would be added and create a new `CommandResult` to be returned. + +==== Design Consideration + +===== Aspect: Implementation of `CreateRouteCommandParser` +* **Alternative 1 (current choice):** `RouteCommandParser` is parsed first, then pass to `CreateRouteCommandParser` +** Pros: Better modularization for `Route` commands, better cohesion, adheres to the Single Responsibility Principle. +** Cons: More complicated file & package structure. +* **Alternative 2:** `CreateRouteCommand` is parsed straight from `OrderBookParser`. +** Pros: Easier to manage the file structure. +** Cons: Poor modularization of the `Command` classes. +// end::route[] + +// tag::assign[] +=== Assign feature +==== Current Implementation +The `/assign` command allows assigning of multiple orders to a delivery man in FoodZoom. -_{Explain here how the data encryption feature will be implemented}_ +The following sequence diagram shows the sequence flow from the `LogicManager` to the `ModelManager` when a user enter a `/assign` command: -// end::dataencryption[] +image::AssignCommandSequenceDiagram1.png[width=800] +Figure 3.7.1.1 Sequence diagram to illustrate `Logic` component interactions for `/assign` command. + +image::AssignCommandSequenceDiagram2.png[width=800] +Figure 3.7.1.2 Sequence diagram to illustrate `Logic` and `Model` component interactions for route `create` command. + +From the sequence diagram: + +. When `LogicManager` receive the `execute` command, it calls the `parseCommand` method in `OrderBookParser`. +. `OrderBookParser` will receive `/assign` as the command and instantiate `AssignCommandParser` to further parse the command. +. If the arguments specified in the `/assign` command are valid, a `AssignCommand` will be created and return back to the `LogicManger` +. `LogicManager` will proceed to call the `execute` command of `AssignCommand` +. `AssignCommand` will proceed to call the `getFilteredOrderList` and `getFilteredDeliverymanList` method of `Model` +. Iterate through the `orderIds`, if valid, add the `order` corresponding to that `index` to the set of `Order`. +. Create a new `Deliveryman` to be assigned from the `deliveryman` corresponding to the deliveryman index. +. Iterate through the orders, call `setDeliveryman`. +. A new route would be added and create a new `CommandResult` to be returned. + +==== Design Consideration + +===== Aspect: Relationship between `Order` and `Deliveryman` +* **Alternative 1 (current choice):** Two way associations between the `Order` and `Deliveryman` +** Pros: Performing update operations such as assigning orders or marking orders as completed and displaying information on each order and +delivery man are more effective as they are aware of one another. +** Cons: More complicated to implement the assigning of order, and subsequently other features regarding this relationship such as removing of order. +* **Alternative 2:** Only the `Deliveryman` is aware of the `Order` +** Pros: Easier to implement the feature and manage the order and deliveryman states. +** Cons: Performing the update operations mentioned above will be ineffective, and it will be more difficult to display information on which delivery man an order is assigned to. + +// end::assign[] === Logging @@ -565,16 +1051,19 @@ Do take a look at <> before attempting to modify the `Logic` compo **** * Hints ** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. +** link:{repoURL}/src/main/java/seedu/address/logic/parser/OrderBookParser.java[`OrderBookParser`] is responsible for +analyzing command words. * Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. +** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/OrderBookParser +.java[`OrderBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute + the same intended command. ** Add new tests for each of the aliases that you have added. ** Update the user guide to document the new aliases. ** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. **** [discrete] -==== `Model` component +==== `Model` *Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. @@ -585,12 +1074,13 @@ Do take a look at <> before attempting to modify the `Model` compo + **** * Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. +** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/OrderBook.java[`OrderBook`] API need to be updated. ** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. +** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/OrderBook +.java[`OrderBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/OrderBook.java[`OrderBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. * Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. +** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/OrderBook.java[`OrderBook`]. Loop through each person, and remove the `tag` from each person. +** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `OrderBook#removeTag(Tag)`. ** Add new tests for each of the new public methods that you have added. ** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. **** @@ -643,7 +1133,7 @@ image::getting-started-ui-result-after.png[width="200"] ** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. ** You can write two different kinds of tests to ensure that the functionality works: *** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. +*** The system tests link:{repoURL}/src/test/java/systemtests/OrderBookSystemTest.java[`OrderBookSystemTest#assertCommandBoxShowsDefaultStyle() and OrderBookkSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. ** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. *** Do read the commits one at a time if you feel overwhelmed. **** @@ -664,9 +1154,10 @@ image::getting-started-ui-status-after.png[width="500"] ** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. * Solution ** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. +** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleOrderBookChangedEvent +(OrderBookChangedEvent)`] to update the number of persons whenever there are new changes to the orderbook. ** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. +** For system tests, modify link:{repoURL}/src/test/java/systemtests/OrderBookSystemTest.java[`OrderBookSystemTest`] to also verify the new total number of persons status bar. ** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. **** @@ -678,12 +1169,12 @@ image::getting-started-ui-status-after.png[width="500"] [TIP] Do take a look at <> before attempting to modify the `Storage` component. -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. +. Add a new method `backupOrderBook(ReadOnlyOrderBook)`, so that the address book can be saved in a fixed temporary location. + **** * Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. +** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/OrderBookStorage.java[`OrderBookStorage`] interface. +** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlOrderBookStorage.java[`XmlOrderBookStorage`] class. * Solution ** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. **** @@ -693,7 +1184,8 @@ Do take a look at <> before attempting to modify the `Storage` c By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. +*Scenario:* You are a software maintainer for `orderbook`, as the former developer team has moved on to new projects. +The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. ==== Description Edits the remark for a person specified in the `INDEX`. + @@ -714,12 +1206,13 @@ Let's start by teaching the application how to parse a `remark` command. We will **Main:** . Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/OrderBookParser.java[`OrderBookParser`] to accept a +`RemarkCommand`. **Tests:** . Add `RemarkCommandTest` that tests that `execute()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. +. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/OrderBookParserTest.java[`OrderBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. ===== [Step 2] Logic: Teach the app to accept 'remark' arguments Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` @@ -728,14 +1221,14 @@ Let's teach the application to parse arguments that our `remark` command will ac . Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. . Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. +. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/OrderBookParser.java[`OrderBookParser`] to use the newly implemented `RemarkCommandParser`. **Tests:** . Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. . Add `RemarkCommandParserTest` that tests different boundary values for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/OrderBookParserTest.java[`OrderBookParserTest`] to test that the correct command is generated according to the user input. ===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. @@ -768,7 +1261,8 @@ Now we have the `Remark` class, we need to actually use it inside link:{repoURL} . Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. . You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) +. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks +for the sample data (delete your `orderBook.xml` so that the application will load the sample data when you launch it.) ===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. @@ -779,7 +1273,7 @@ We now have `Remark` s for `Person` s, but they will be gone when we exit the ap **Tests:** -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +. Fix `invalidAndValidPersonOrderBook.xml`, `typicalPersonsOrderBook.xml`, `validOrderBook.xml` etc., such that the XML tests will not fail due to a missing `` element. ===== [Step 6b] Test: Add withRemark() for `PersonBuilder` Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. @@ -820,13 +1314,15 @@ See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step- *Target user profile*: -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input +* Business Owners and Delivery Man +* Needs to manage a large collection of orders +* Needs to dispatch orders to relevant delivery man +* Wants to view route information and calculate timings +* Wants insights on purchases and order history * is reasonably comfortable using CLI apps -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app + +*Value proposition*: Integrate end-to-end systems and streamline food delivery management [appendix] == User Stories @@ -836,62 +1332,196 @@ 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 -|`* * *` |user |add a new person | -|`* * *` |user |delete a person |remove entries that I no longer need +|`* * *` |new manager |see usage instructions |refer to instructions when I forget how to use the App -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +|`* * *` |manager |login/logout/signup of application |able to protect my data and access it when I need do -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +|`* * *` |manager |add a new customer order | can track the current orders I +have -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +|`* * *` |manager |add or remove a deliveryman | can manage the deliverymen available to dispatch orders + +|`* * *` |manager |assign orders to available deliverymen | can send out orders and fulfil customer requests + +|`* * *` |manager |delete an order | can cancel orders that should not be in the queue + +|`* * *` |manager |find an order using phone number | can find specific orders quickly + +|`* *` |manager |update my menu (add/remove food items) | can manage the food items I have and expand by business + +|`* *` |manager |view a basic route for order | have a clearer idea on how to dispatch my deliverymen + +|`* *` |manager |find orders by more files (name, food item etc.) | have more options to locate specific orders + +|`* *` |manager |trace status of orders (estimated timeline) | can have visibility on when orders will be delivered + +|`* *` |manager |have a dashboard to view succesful and failed orders | can have insights on how my outlet is doing + +|`* *` |manager |have a dynamic indication of which deliveryman is available | can send out multiple orders to many deliverymen and have improved service + +|`* *` |manager |view individual customer order history | gain insights on +what specific customers are ordering + +|`* *` |manager |find my deliverman by name | find details about the deliveryman, + +|`* *` |manager |see a thumbnail of deliveryman | can easily tell who is who in one glance + +|`* *` |manager |edit my order | make changes to the order + +|`*` |manager |sms confirmation on succesful order | have visibility on how my order delivery service is doing + +|`*` |manager |view a map with placeholders showing orders/deliverymen routes | have immediate view on delivery status and gain insights on my service + +|`* * *` |new deliveryman |see usage instructions |refer to instructions when I forget how to use the App + +|`* * *` |deliveryman |login/logout of application |able to protect my data and access it when I need do + +|`* * *` |deliveryman |list assigned orders |can tell what are the jobs I need to complete + +|`* * *` |deliveryman |get customer contact for orders |can contact customer if there is a need (delivery delay/incorrect address/queries etc.) -_{More to be added}_ +|`* *` |deliveryman |view delivery route for orders |dont have to consult external service to know how to reach the destination + +|`* *` |deliveryman |confirm succesful order in system |the manager can track the completion of orders + +|`*` |deliveryman |send feedback on delivery process |the manager can relate and improve the dispatch service + +|======================================================================= [appendix] == Use Cases -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +(For the use cases below, the *System* is FoodZoom and the *Actor* is the Managers) [discrete] -=== Use case: Delete person +=== Use Case: Add Order *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. Manager requests to add order to the FoodZoom +2. FoodZoom checks the format of the add command +3. FoodZoom adds the order to the database +4. FoodZoom shows success message to the manager + Use case ends. *Extensions* -[none] -* 2a. The list is empty. +* 2a. Add command is invalid +* 2b. FoodZoom shows an error message to the manager ++ +Use case ends. + + +[discrete] +=== Use Case: Find orders by phone number + +*MSS* + +1. Manager request to list orders by phone number +2. FoodZoom shows a list of orders and their status +3. Use case ends. + +*Extensions* + +* 1a. The phone number not found in the list of orders. ++ +Use case ends. + +[discrete] +=== Use Case: Assign Order to Delivery man + +*MSS* + +1. Manager requests to view pending orders +2. FoodZoom lists all orders +3. Manager adds pending orders to planner +4. FoodZoom shows travel route and timing details to Manager for all orders added +5. Manager requests to add route to available deliverymen +6. FoodZoom adds route to delivery man and displays on same screen +7. Manager requests to show routing information +8. Manager shown a screen showing what each deliveryman is assigned ++ +Use case ends. + +*Extension* + +* 2a. No available orders + Use case ends. -* 3a. The given index is invalid. +* 4a. Travel Details cannot be fetched +** 4b. FoodZoom shows relevant error message to user + -[none] -** 3a1. AddressBook shows an error message. +Use case ends. + +* 5a. No available delivery-men +** 5b. FoodZoom shows error message ++ +Use case ends. + +[discrete] +=== Use Case: Delete Order + +*MSS* + +1. Manager requests to find the order by phone number +2. FoodZoom shows the order +3. Manager requests to delete the order +4. FoodZoom deletes the order +5. FoodZoom shows message order successfully deleted ++ +Use case ends. + +*Extension* + +* The order does not exist. +** 1a. FoodZoom shows message that order does not exist ++ +Use case ends + +(For the use cases below, the *System* is FoodZoom and the *Actor* is the Delivery-men) + +[discrete] +=== Use Case: View Assigned Route + +*MSS* + +1. Delivery man logs in to the app +2. FoodZoom shows if delivery man has routes available +3. Delivery man requests for assigned route +4. FoodZoom shows the assigned route +5. Delivery man looks through each destination and checks address info +6. Delivery man checks for status of food +7. Deliveryman logs out to collect food & leave for delivery ++ +Use case ends. + +*Extensions* + +* 2a. Delivery man doesn't have any routes ++ +Use case ends + +* 4a. Location info is unclear to deliveryman +** 4b. Delivery man searches for order to get customer's contact +** 4c. Delivery man contacts customer to clarify location + -Use case resumes at step 2. +Use case ends -_{More to be added}_ [appendix] == Non Functional Requirements . Should work on any <> as long as it has Java `9` or higher installed. -. Should be able to hold up to 1000 persons 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. +. Should be able to hold up to 1000 orders and should respond within 2 seconds +. The system should work on both 32-bit and 64-bit environments. +. The system should only show data to the people with the <> +. The system should persist data about orders for the past year + -_{More to be added}_ [appendix] == Glossary @@ -899,8 +1529,9 @@ _{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 +[[appropriate-roles]] Appropriate roles:: +Manager, Delivery Men + [appendix] == Product Survey @@ -941,26 +1572,345 @@ These instructions only provide a starting point for testers to work on; testers .. Re-launch the app by double-clicking the jar file. + Expected: The most recent window size and location is retained. -_{ more test cases ... }_ +=== Login into account + +. Login into the application +.. Prerequisites: Needs to be logged out of the application. +... Test case: Invalid username and valid password + +Command: `/login u/1 pw/password123` + +Expected: Cannot login because username is too short and it needs to be 3 - 64 characters. + +... Test case: Username with 65 characters (invalid) and valid password + +Command: `/login u/012345678901234567890123456789012345678901234567890123456789012345 pw/password123` + +Expected: Cannot login because username is too long and it needs to be 3 - 64 characters. + +... Test case: Valid username and invalid password + +Command: `/login u/lucaskoh pw/123` + +Expected: Cannot login because password is too short and it needs to be at least 6 characters. + +... Test case: Valid username and 6 characters password (valid) + +Command: `/login u/lucaskoh pw/123456` + +Expected: Cannot login because account does not exist. + +.. Prerequisites: Application comes with default valid user. +... Test case: Valid username and valid password + +Command: `/login u/alicepauline pw/alicepauline01` + +Expected: Successfully login into the application because of valid username and password and user exist in application. The application will bring you to the home page. + + +=== Sign up for an account +. Sign up for an account +.. Prerequisites: No exisiting account in application. +... Test case: Invalid name, valid username, valid password + +Command: `/signup n/lu u/lucaskoh pw/123456` + +Expected: Fail to sign up. Name is too short and it needs to be 3 - 64 characters long. + +... Test case: Invalid name(65 characters long), valid username, valid password + +Command: `/signup n/012345678901234567890123456789012345678901234567890123456789012345 u/lucaskoh pw/123456` + +Expected: Fail to sign up. Name is too long and it needs to be 3 - 64 characters long. + +... Test case: Valid name, invalid username, valid password + +Command: `/signup n/LucasKoh u/@1234~2! pw/123456` + +Expected: Fail to sign up. Username should only contain alphanumeric characters. + +... Test case: Valid name, valid username, invalid password + +Command: `/signup n/LucasKoh u/lucaskoh pw/!!@@15` + +Expected: Fail to sign up. Password should only contain alphanumeric characters. + +... Test case: Valid name, valid username, valid password + +Command: `/signup n/Lucas Koh u/lucaskoh pw/lucaskoh54321` + +Expected: Successfully sign up for account. Application will automatically logged you into the home page. + +.. Prerequisites: Already have `n/Alice Pauline u/alicepauline pw/alicepauline01` account within the application. +... Test case: Different name, same username and same password + +Command: `/signup n/Alicepauline u/alicepauline pw/alicepauline01` + +Expected: Fail to sign up. User already exist within the application. + +=== Adding an Order +.. Prerequisites: Logged into the application. An order with name "John Lee", phone "98765432" and date "20-10-2018 +00:00:00" does not already exist. To check, type `/order list` to see all existing order. + +... Test case: `/order add n/John Lee p/98765432 a/311, Clementi Ave 2, #02-25, 612344 f/Roti Prata f/Ice Milo +dt/20-10-2018 00:00:00` + + Expected: A new order with the above details is added to the list. The command box should reflect this accordingly + with the message "New order added: " +
. The map should show a new placeholder (or an existing + placeholder will increase in size). Timestamp in the status bar is updated. + +... Test case: `/order add n/John Lee p/98765432 a/311, Clementi Ave 2, #02-25, 612344 f/Roti Prata f/Ice Milo +dt/21-10-2018 00:00:00` + + Expected: A new order with the above details is added to the list. The command box should reflect this accordingly + with the message "New order added: " +
. The map should show a new placeholder (or an + existing placeholder will increase in size) that gives an approximation of location. Timestamp in the + status bar is updated. + +... Test case: `/order add n/John Lee p/98765432 a/311, Clementi Ave 2, #02-25, 612344 f/Roti Prata f/Ice Milo + dt/32-10-2018 00:00:00` + + Expected: No order is added. Error details shown in the status message (invalid date). Status bar remains the same. + +... Test case: `/order add n/John Lee p/98765432 a/311, Clementi Ave 2, #02-25, 612344 dt/32-10-2018 00:00:00` + + Expected: No order is added. Error details shown in the status message (no food). Status bar remains the same. + +... Test case: `/order add n/John Lee n/John Roe p/98765432 a/311, Clementi Ave 2, #02-25, 612344 f/Roti Prata f/Ice +Milo dt/30-10-2018 00:00:05` + + Expected: A new order with the above details is added to the list. The name is taken as the last name given which + is John Roe. The command box should reflect this accordingly + with the message "New order added: " +
. The map should show a new placeholder (or an + existing placeholder will increase in size) that gives an approximation of location. Timestamp in the + status bar is updated. + +... Test case: `/order add n/John Lee n/John Roe p/98765432 a/311, Clementi Ave 2, #02-25, 54612344 f/Roti Prata f/Ice +Milo dt/30-10-2018 00:00:01` + + Expected: A new order with the above details is added to the list. The command box should reflect this accordingly + with the message "New order added: " +
. The map should show a new placeholder (or an + existing placeholder will increase in size) that gives an approximation of location. The location will + point to postal code `612344` as only last six digits are considered. Timestamp in the + status bar is updated. + + +.. Prerequisites: An order with name "John Lee", phone "98765432" and date "20-10-2018 + 00:00:00" already exists +... Test case: `/order add n/John Lee p/98765432 a/311, Clementi Ave 2, #02-25, 612344 f/Roti Prata f/Ice Milo dt/20-10-2018 00:00:00` + + Expected: No order is added. Error details shown in the status message (duplicate order). Status bar remains the + same. + + +=== Selecting order +.. Prerequisites: Logged into the application. There is at least 1 order being displayed in the list. + +... Test case: `/order select 1` + + Expected: The selected order would be displayed on the display panel. + +... Test case: `/order select 0` + + Expected: No order would be selected and displayed as this is an invalid index. + +=== Finding orders +. Finding order by phone +.. Prerequisites: Having at least 1 order. +.. Test case: Valid phone without space + + Command: `/order find p/9111` + + Expected: Order/s with the phone `9111` and `911122113` is shown on the left panel. + +.. Test case: Valid phones with space + + Command: `/order find p/1234 9111` + + Expected: Order/s with the phone `9111`, `911122113`, `1234`, `12345678` is shown on the left panel. + +.. Test case: Empty value + + Command: `/order find p/` + + Expected: Error is shown in the status message. Value cannot be empty. + +. Finding order by address, name, food +.. Prerequisites: Having at least 1 order. +.. Test case: Valid address + + Command: `/order find a/Blk 10` + + Expected: Order/s with the address containing `Blk 10` is shown on the left panel. + +.. Test case: Valid food + + Command: `/order find f/Chicken Rice` + + Expected: Order/s with food `Chicken Rice`, `Chicken Rice with Chilli` is shown on the left panel. + +.. Test case: Valid name + + Command: `/order find n/Alex Yeoh` + + Expected: Order/s with name `Alex Yeoh`, `Alex Yeoh Jin` is shown on the left panel. + +. Finding order by status +.. Prerequisites: Logged into the application. Having orders in the list with 3 different status +.. Test case: Exact Status + + Command: `/order find st/PENDING` + + Expected: Order/s with status `PENDING` is shown on the left panel. + +.. Test case: Exact status in lower case + + Command: `/order find st/pending` + + Expected: Order/s with status `PENDING` is shown on the left panel. + +.. Test case: Empty status value + + Command: `/order find st/` + + Expected: Error is shown in the status message. Value cannot be empty. + +.. Test case: Invalid status + + Command: `/order find st/abc` + + Expected: Error is shown in the status message. Only valid status are allowed. + +. Finding order by date +.. Prerequisites: Logged into the application. Having orders added with different dates +.. Test case: Find with one datetime parameter + + Command: `/order find dt/01-10-2018 10:00:00` + + Expected: Order/s with the datetime `01-10-2018 10:00:00` is shown on the left panel. + +.. Test case: Find with two datetime parameter + + Command: `/order find dt/01-10-2018 10:00:00 dt/03-10-2018 14:00:00` + + Expected: Order/s within and on the datetime `01-10-2018 10:00:00` and `03-10-2018 10:00:00` is shown on the left panel. + +.. Test case: Find with more than 2 datetime parameter + + Command: `/order find dt/01-10-2018 10:00:00 dt/03-10-2018 14:00:00 dt/02-10-2018 10:00:00` + + Expected: Order/s within and on the datetime `01-10-2018 10:00:00` and `03-10-2018 10:00:00` is shown on the left panel. + +.. Test case: Empty date value + + Command: `/order find dt/` + + Expected: Error is shown in the status message. Value cannot be empty. + +.. Test case: Only date is given. No time is given + + Command: `/order find dt/01-10-2018` + + Expected: Error is shown in the status message. Date should follow the format `dd-MM-yyy HH:mm:ss`. + +.. Test case: Invalid datetime is given + + Command: `/order find dt/57-10-2018 10:00:00` + + Expected: Error is shown in the status message. Date should be a valid date. + +. Finding order with more than 1 argument +.. Prerequisites: Logged into the application. Having at least 1 order +.. Test case: Valid name and status value + + Command: `/order find n/john st/pending` + + Expected: Order/s with the name `john` and the status `PENDING` is shown on the left panel. + +.. Test case: 2 names field are specified. + + Command: `/order find n/john n/tom` + + Expected: Only order/s with the name `tom` is shown on the left panel. Only the last field is taken. + +=== Editing an order +. Edit an order while all orders are listed +.. Prerequisites: Logged into the application. List all orders using the `/order list` command. Multiple orders in the + list. +... Test case: Valid arguments + + Command: `/order edit f/Roti Canai` + + Expected: Food of the order with the `` will be overwritten with the food `Roti Canai` + +... Test case: Invalid order index + + Command: `/order edit 0 n/alex` + + Expected: Cannot edit as the order index is a non-zero unsigned integer. + +... Other incorrect edit commands to try: `/order edit x f/alex` (where x is larger than the list on the left) + + Expected: Cannot edit as the order index must be a valid index in the list shown on the left. + +.. Prerequisites: Logged into the application. There is an order with the name `alex`, phone `12345678` and datetime +`01-10-2018 10:00:00` +... Test case: Edit an order to an existing order in the list + + Command: `/order edit 1 n/alex p/12345678 dt/01-10-2018 10:00:00` + + Expected: Cannot edit as an order with the same name, phone and datetime is already in the list of orders. + +.. Prerequisites: Logged into the application. Have orders that are `Ongoing` or `Completed` +... Test case: Edit an order with status `Ongoing` or `Completed` + + Command: `/order edit f/tea` + + Expected: Cannot edit as the order is already assign to a deliveryman. + +=== Deleting an order + +. Deleting an order while all order are listed + +.. Prerequisites: Logged into the application. List all orders using the `/order list` command. Multiple orders in the + list. The order to be deleted is `Pending` or `Completed`. +.. Test case: `/order delete ` + + Expected: That order is deleted from the list. Details of the deleted order shown in the status message. + Timestamp in the status bar is updated. +.. Test case: `/order delete 0` + + Expected: No order is deleted. Error details shown in the status message. Status bar remains the same. +.. Other incorrect delete commands to try: `/order delete`, `/order delete x` (where x is larger than the list size) +_{give more}_ + + Expected: Similar to previous. +.. Test case: Logged into the application. Deleting an `Ongoing` order + + Command: `/order delete o/` + Expected: Unable to delete the order because it is already assigned to a deliveryman. -=== Deleting a person +=== Mark an order as Completed -. Deleting a person while all persons are listed +. Marking an order as `Completed` +.. Prerequisites: Logged into the application. Already have an `Ongoing` order. +... Test case: Mark an invalid index as completed + +Command: `/order done 0` + +Expected: No order is marked as completed. Index have to a positive integer. -.. 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}_ + - Expected: Similar to previous. +... Test case: Mark an valid index `Ongoing` order as completed + +Command: `/order done ` + +Expected: Order on order list panel is marked as `Completed` and deliveryman is updated (Deliveryman total assigned +orders will decrease by `1` or become `available`). + +.. Prerequisites: Logged into the application. Have some `Pending` orders. +... Test case: Mark a valid index as completed but would fail. + +Command: `/order done ` + +Expected: Fail to mark the order as `Completed` because only `ongoing` status can be marked as `completed`. + +.. Prerequisites: Logged into the application. Have some `Completed` orders. +... Test case: Mark a valid index as completed but would fail. + +Command: `/order done ` + +Expected: Fail to mark the order as `Completed` because it is already `completed`. + +=== Assigning orders to a deliveryman + +. Assigning multiple pending orders to a deliveryman + +.. Prerequisite: Logged into the application. Have orders that are `Pending`, and a deliveryman + + Test case: Assigning two `Pending` orders to a deliveryman + + Command: `/assign o/ o/ d/1` + + Expected: First deliveryman's assigned order count increases by 2, the two orders change status to become `Ongoing`. + +.. Prerequisite: Logged into the application. Have a deliveryman + + Test case: Invalid order or deliveryman index + + Command: `/assign o/0 d/1`, `/assign o/1 d/0` + + Expected: Cannot assign because the order index is not a non-zero unsigned integer. + +.. Prerequisite: Logged into the application. Have an `Ongoing` or `Completed` order + + Test case: Assigning an `Ongoing` or `Completed` order to a deliveryman + + Command: `/assign o/ d/1` + + Expected: Cannot assign because that order was already assigned to a deliveryman previously. + +=== Adding deliveryman + +.. Prerequisites: Logged into the application. Name "John" has not been used for any existing deliverymen. To check, +type `/deliveryman list` to see +all existing deliverymen. + +... Test case: `/deliveryman add n/John` + + Expected: A new deliveryman named 'John' is added to the list. The command box should reflect this accordingly + with the message "New deliveryman added: John". Timestamp in the status bar is updated. + +... Test case: `/deliveryman add n/John-Paul` + + Expected: No deliveryman is added. Error details shown in the status message - non-alphanumeric characters are not allowed. + Status bar remains the same. + +.. Prerequisites: Logged into the application. A deliveryman named "John" exists in the system. + +... Test case: `/deliveryman add n/John` + + Expected: No deliveryman is added. Error details shown in the status message. + Status bar remains the same. + +=== Deleting deliveryman + +.. Prerequisites: Logged into the application. There is 1 deliveryman being displayed in the list. This deliveryman +must not have any orders assigned to him too. +To achieve this, you can filter deliverymen by name, for example, using the default deliverymen, you can try `/deliveryman find n/Chi Kao` + +... Test case: `/deliveryman delete 0` + + Expected: No deliveryman would be deleted as the index is invalid. + +... Test case: `/deliveryman delete 2` + + Expected: No deliveryman would be deleted as the index is invalid. + +... Test case: `/deliveryman delete 1` + + Expected: The deliveryman displayed will be deleted. + +.. Prerequisites: Logged into the application. There is 1 deliveryman being displayed in the list. This deliveryman +has an order assigned to him. +To achieve this, you can filter deliveryman by name, then assign him a `Pending` order, for example `/assign o/1 d/1`. + +... Test case: `/deliveryman delete 1` + + Expected: Deliveryman would not be deleted as you are not allowed to delete deliveryman with orders assigned. + +=== Selecting deliveryman +.. Prerequisites: Logged into the application. There is at least 1 deliveryman being displayed in the list. + +... Test case: `/deliveryman select 1` + + Expected: The selected deliveryman would be displayed on the display panel. -_{ more test cases ... }_ +... Test case: `/deliveryman select 0` + + Expected: No deliveryman would be selected and displayed as this is an invalid index. -=== Saving data +=== Finding deliveryman +.. Prerequisite: Logged into the application. Having at least one deliveryman. -. Dealing with missing/corrupted data files +... Test case: `/deliveryman find n/tom` + + Expected: Deliveryman with the name `tom`, `tommy`, `tom hanks` would be displayed on the display panel. -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +... Test case: `/deliveryman find n/tom n/john` + + Expected: Deliveryman with the name `john`, `johnny`, `john doe` would be displayed on the display panel. Only the last field is taken. -_{ more test cases ... }_ +... Test case: `/deliveryman find n/` + + Expected: Error is shown in the status message. Value cannot be empty. diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc deleted file mode 100644 index 83cda0927226..000000000000 --- a/docs/LearningOutcomes.adoc +++ /dev/null @@ -1,266 +0,0 @@ -= Learning Outcomes -:site-section: LearningOutcomes -:toc: macro -:toc-title: -:toclevels: 1 -:sectnums: -:sectnumlevels: 1 -:imagesDir: images -:stylesDir: stylesheets -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master - -After studying this code and completing the corresponding exercises, you should be able to, - -toc::[] - -''' - -== Use High-Level Designs `[LO-HighLevelDesign]` - -Note how the <> describes the high-level design using an _Architecture Diagrams_ and high-level sequence diagrams. - -*Resources* - -* https://se-edu.github.io/se-book/architecture/[se-edu/se-book: Design: Architecture] -* https://se-edu.github.io/se-book/design/introduction/multilevelDesign/[se-edu/se-book: Design: Introduction: Multi-Level Design] - -''' - -== Use Event-Driven Programming `[LO-EventDriven]` - -Note how the <> uses events to communicate with components without needing a direct coupling. Also note how the link:{repoURL}/src/main/java/seedu/address/commons/core/index/EventsCenter.java[`EventsCenter.java`] acts as an event dispatcher to facilitate communication between event creators and event consumers. - -*Resources* - -* https://se-edu.github.io/se-book/architecture/architecturalStyles/eventDriven/[se-edu/se-book: Design: Architecture: Architecture Styles: Event-Driven Architectural Style] - -''' - -== Use API Design `[LO-ApiDesign]` - -Note how components of AddressBook have well-defined APIs. For example, the API of the `Logic` component is given in the link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -image:LogicClassDiagram.png[width="800"] - -*Resources* - -* https://se-edu.github.io/se-book/reuse/apis/[se-edu/se-book: Implementation: Reuse: APIs] - -''' - -== Use Assertions `[LO-Assertions]` - -Note how the AddressBook app uses Java ``assert``s to verify assumptions. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/assertions/[se-edu/se-book: Implementation: Error Handling: Assertions] - -=== Exercise: Add more assertions - -* Make sure assertions are enabled in your IDE by forcing an assertion failure (e.g. add `assert false;` somewhere in the code and run the code to ensure the runtime reports an assertion failure). -* Add more assertions to AddressBook as you see fit. - - -''' - -== Use Logging `[LO-Logging]` - -Note <>. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/logging/[se-edu/se-book: Implementation: Error Handling: Logging] - -=== Exercise: Add more logging - -Add more logging to AddressBook as you see fit. - - -''' - -== Use Defensive Coding `[LO-DefensiveCoding]` - -Note how AddressBook uses the `ReadOnly*` interfaces to prevent objects being modified by clients who are not supposed to modify them. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/defensiveProgramming/[se-edu/se-book: Implementation: Error Handling: Defensive Programming] - -=== Exercise: identify more places for defensive coding - -Analyze the AddressBook code/design to identify, - -* where defensive coding is used -* where the code can be more defensive - -''' - -== Use Build Automation `[LO-BuildAutomation]` - -Note <>. - -*Resources* - -* https://se-edu.github.io/se-book/integration/buildAutomation/what/[se-edu/se-book: Implementation: Integration: Build Automation: What] - -=== Exercise: Use gradle to run tasks - -* Use gradle to do these tasks: Run all tests in headless mode, build the jar file. - -=== Exercise: Use gradle to manage dependencies - -* Note how the build script `build.gradle` file manages third party dependencies such as ControlsFx. Update that file to manage a third-party library dependency. - - -''' - -== Use Continuous Integration `[LO-ContinuousIntegration]` - -Note <>. (https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]]) - -*Resources* - -* https://se-edu.github.io/se-book/integration/buildAutomation/continuousIntegrationDeployment/[se-edu/se-book: Implementation: Integration: Build Automation: CI & CD] - -=== Exercise: Use Travis in your own project - -* Set up Travis to perform CI on your own fork. - - -''' - -== Use Code Coverage `[LO-CodeCoverage]` - -Note how our CI server <>. (https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]]) After <> for your project, you can visit Coveralls website to find details about the coverage of code pushed to your repo. https://coveralls.io/github/se-edu/addressbook-level4?branch=master[Here] is an example. - -*Resources* - -* https://se-edu.github.io/se-book/testing/testCoverage/[se-edu/se-book: QA: Testing: Test Coverage] - -=== Exercise: Use the IDE to measure coverage locally - -* Use the IDE to measure code coverage of your tests. - -''' - -== Apply Test Case Design Heuristics `[LO-TestCaseDesignHeuristics]` - -The link:{repoURL}/src/test/java/seedu/address/commons/util/StringUtilTest.java[`StringUtilTest.java`] -class gives some examples of how to use _Equivalence Partitions_, _Boundary Value Analysis_, and _Test Input Combination Heuristics_ to improve the efficiency and effectiveness of test cases testing the link:../src/main/java/seedu/address/commons/util/StringUtil.java[`StringUtil.java`] class. - -*Resources* - -* https://se-edu.github.io/se-book/testCaseDesign/[se-edu/se-book: QA: Test Case Design] - -=== Exercise: Apply Test Case Design Heuristics to other places - -* Use the test case design heuristics mentioned above to improve test cases in other places. - -''' - -== Write Integration Tests `[LO-IntegrationTests]` - -Consider the link:{repoURL}/src/test/java/seedu/address/storage/StorageManagerTest.java[`StorageManagerTest.java`] class. - -* Test methods `prefsReadSave()` and `addressBookReadSave()` are integration tests. Note how they simply test if The `StorageManager` class is correctly wired to its dependencies. -* Test method `handleAddressBookChangedEvent_exceptionThrown_eventRaised()` is a unit test because it uses _dependency injection_ to isolate the SUT `StorageManager#handleAddressBookChangedEvent(...)` from its dependencies. - -Compare the above with link:{repoURL}/src/test/java/seedu/address/logic/LogicManagerTest.java[`LogicManagerTest`]. Some of the tests in that class (e.g. `execute_*` methods) are neither integration nor unit tests. They are _integration + unit_ tests because they not only check if the LogicManager is correctly wired to its dependencies, but also checks the working of its dependencies. For example, the following two lines test the `LogicManager` but also the `Parser`. - -[source,java] ----- -@Test -public void execute_invalidCommandFormat_throwsParseException() { - ... - assertParseException(invalidCommand, MESSAGE_UNKNOWN_COMMAND); - assertHistoryCorrect(invalidCommand); -} ----- - -*Resources* - -* https://se-edu.github.io/se-book/testing/testingTypes/[se-edu/se-book: QA: Testing: Testing Types] - -=== Exercise: Write unit and integration tests for the same method. - -* Write a unit test for a high-level method somewhere in the code base (or a new method you wrote). -* Write an integration test for the same method. - -''' - -== Write System Tests `[LO-SystemTesting]` - -Note how tests below `src/test/java/systemtests` package (e.g link:{repoURL}/src/test/java/systemtests/AddCommandSystemTest.java[`AddCommandSystemTest.java`]) are system tests because they test the entire system end-to-end. - -*Resources* - -* https://se-edu.github.io/se-book/testing/testingTypes/[se-edu/se-book: QA: Testing: Testing Types] - -=== Exercise: Write more system tests - -* Write system tests for the new features you add. - -''' - -== Automate GUI Testing `[LO-AutomateGuiTesting]` - -Note how this project uses TextFX library to automate GUI testing, including <>. - -=== Exercise: Write more automated GUI tests - -* Covered by `[LO-SystemTesting]` - -''' - -== Apply Design Patterns `[LO-DesignPatterns]` - -Here are some example design patterns used in the code base. - -* *Singleton Pattern* : link:{repoURL}/src/main/java/seedu/address/commons/core/EventsCenter.java[`EventsCenter.java`] is Singleton class. Its single instance can be accessed using the `EventsCenter.getInstance()` method. -* *Facade Pattern* : link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager.java`] is not only shielding the internals of the Storage component from outsiders, it is mostly redirecting method calls to its internal components (i.e. minimal logic in the class itself). Therefore, `StorageManager` can be considered a Facade class. -* *Command Pattern* : The link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command.java`] and its sub classes implement the Command Pattern. -* *Observer Pattern* : The <> used by this code base employs the Observer pattern. For example, objects that are interested in events need to have the `@Subscribe` annotation in the class (this is similar to implementing an `\<>` interface) and register with the `EventsCenter`. When something noteworthy happens, an event is raised and the `EventsCenter` notifies all relevant subscribers. Unlike in the Observer pattern in which the `\<>` class is notifying all `\<>` objects, here the `\<>` classes simply raises an event and the `EventsCenter` takes care of the notifications. -* *MVC Pattern* : -** The 'View' part of the application is mostly in the `.fxml` files in the `src/main/resources/view` folder. -** `Model` component contains the 'Model'. However, note that it is possible to view the `Logic` as the model because it hides the `Model` behind it and the view has to go through the `Logic` to access the `Model`. -** Sub classes of link:{repoURL}/src/main/java/seedu/address/ui/UiPart.java[`UiPart`] (e.g. `PersonListPanel` ) act as 'Controllers', each controlling some part of the UI and communicating with the 'Model' (via the `Logic` component which sits between the 'Controller' and the 'Model'). -* *Abstraction Occurrence Pattern* : Not currently used in the app. - -*Resources* - -* https://se-edu.github.io/se-book/designPatterns/[se-edu/se-book: Design: Design Patterns] - -=== Exercise: Discover other possible applications of the patterns - -* Find other possible applications of the patterns to improve the current design. e.g. where else in the design can you apply the Singleton pattern? -* Discuss pros and cons of applying the pattern in each of the situations you found in the previous step. - -=== Exercise: Find more applicable patterns - -* Learn other _Gang of Four_ Design patterns to see if they are applicable to the app. - -''' - -== Use Static Analysis `[LO-StaticAnalysis]` - -Note how this project uses the http://checkstyle.sourceforge.net/[CheckStyle] static analysis tool to confirm compliance with the coding standard. - -*Resources* - -* https://se-edu.github.io/se-book/qualityAssurance/staticAnalysis/[se-edu/se-book: QA: Static Analysis] - -=== Exercise: Use CheckStyle locally to check style compliance - -* Install the CheckStyle plugin for your IDE and use it to check compliance of your code with our style rules (given in `/config/checkstyle/checkstyle.xml`). - -''' - -== Do Code Reviews `[LO-CodeReview]` - -* Note how some PRs in this project have been reviewed by other developers. Here is an https://github.com/se-edu/addressbook-level4/pull/147[example]. -* Also note how we have used https://www.codacy.com[Codacy] to do automate some part of the code review workload (https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]]) - - -=== Exercise: Review a PR - -* Review PRs created by team members. diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 7e0070e12f49..3ebe683395f2 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += FoodZoom - User Guide :site-section: UserGuide :toc: :toc-title: @@ -12,228 +12,552 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2103-AY1819S1-T16-3/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `T16-3` Since: `Aug 2018` Licence: `MIT` == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 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, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +FoodZoom is a food delivery management platform targeted at food business looking to streamline their delivery process. It is responsive and quick, allowing manager to plan their routes and dispatch orders to delivery man *using a Command Line Interface* (CLI) while having the benefits of a Graphical User Interface (GUI). The delivery man can also this application to view their delivery routes and increase their efficiency. == Quick Start . Ensure you have Java version `9` or later 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. -. Double-click the file to start the app. The GUI should appear in a few seconds. +. Download the latest `foodzoom.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your FoodZoom. +. Double-click the file to start the app. The GUI should appear in a few seconds (the below screen is after you signup/login). + image::Ui.png[width="790"] + . 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. +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 -* *`exit`* : exits the app +* *`/signup n/John Doe u/johndoe pw/johndoepassword`*: Sign into the application +* *`/order list`* : lists all orders +* *`/order find n/david`* : finds the order david has ordered +* *`/exit`* : exits the app . Refer to <> for details of each command. [[Features]] == Features +Before starting on the application, read through the below sections. They cover +the format to be used for all the commands and the syntax that is to be followed. + ==== *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 `{nbsp}` (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. +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `/order add n/NAME`, `NAME` is a parameter which can be used as `/order add n/John Doe`. +* Items in square brackets are optional e.g `n/NAME [p/PHONE]` can be used as `n/John Doe p/9112` or as `n/John Doe`. +* Parameters can be in any order e.g. if the command specifies `n/NAME a/ADDRESS`, `a/ADDRESS n/NAME` is also acceptable. +==== + +==== +*Fields restrictions* +[width="100%",cols="20%,<80%"] +|======================================================================= + +|NAME | Can only be alphanumeric and spaces are allowed, it should not be blank and be 1 to 64 characters long + +|PHONE | Phone should only contain numbers, and it should be at least 3 digits long + +|ADDRESS | Can take any values, and it should not be blank. The last 6 characters should be a valid postal code + +|FOOD | Should only contain alphanumeric characters and spaces, and it should not be blank + +|DATETIME | Must conform to the format `dd-MM-YYYY h:m:s`, where _dd_ is date, _MM_ is month, _YYYY_ is year, _h_ is 24 hour of the day, _m_ is minutes and _s_ is seconds. e.g. `14-10-2018 23:30:00` + +|ORDER_STATUS | Only `PENDING`, `ONGOING` or `COMPLETED` are allowed + +|USERNAME | Can be alphanumeric and spaces are *NOT* allowed, it should not be blank and be 3 to 64 characters long. + +|PASSWORD | Can be alphanumeric, should be at least *6* characters long and spaces are *NOT* allowed. Not encrypted +in current releases. + +|INDEX | Refers to the index number shown by the order / deliveryman list command & it must be a positive integer 1, 2, 3, ... and no larger than 2147483647. + +|======================================================================= +==== + +==== +*Available Commands* +[cols="s,a"] +|======================================================================= + +| That don't require login +|`/help` + +`/signup` + +`/login` + +`/logout` + +`/exit` + +`/history` + +|That require login +| `/order add` + +`/order list` + +`/order edit` + +`/order find` + +`/order select` + +`/order delete` + +`/order clear` + +`/order done` + +`/deliveryman add` + +`/deliveryman list` + +`/deliveryman find` + +`/deliveryman select` + +`/deliveryman delete` + +`/assign` + +`/home` + +|======================================================================= ==== -=== Viewing help : `help` +=== Viewing help : `/help` `[Since v1.1]` -Format: `help` +Format: `/help` -=== Adding a person: `add` +// tag::signup[] +=== Sign up for system : `/signup` `[Since v1.1]` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Sign up for a new manager account so that the manager can use the application. Once you sign up, you will be +automatically logged into the application. Since the managers using the application is working for one stall, they will have access to that stall data and see the same home screen -[TIP] -A person can have any number of tags (including 0) +Format: `/signup n/NAME u/USERNAME pw/PASSWORD` 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` +* `/signup n/John Doe u/johndoe pw/johndoepassword` -=== Listing all persons : `list` +// end::signup[] -Shows a list of all persons in the address book. + -Format: `list` +// tag::login[] +=== Login to system : `/login` `[Since v1.1]` -=== Editing a person : `edit` +Login into the application so that the manager can use the application. Once you login, you can use the authenticated + commands. Since the managers using the application is working for one stall, they will have access to that stall data and see the same home screen -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Format: `/login u/USERNAME pw/PASSWORD` +Examples: + +* `/login u/manager pw/password` + +// end::login[] + +// tag::logout[] +=== Logout from system : `/logout` `[Since v1.3]` + +Logout from the application. + +Format: `/logout` + +// end::logout[] + +// tag::home[] +=== Return to Home Page : `/home` `[Since v1.3]` + +If any selection is done, allows you to return back to the home page with the dashboard + +Format: `/home` + +**** +* Renders a bar graph which shows the order history (limited to the latest 7 orders by date) +* Shows a map - the map gives a reasonable *estimate* for the locations of the pending orders. It is +designed to work offline and give you a rough gauge of where your orders are +* This dashboard dynamically updates itself when the order list is changed +**** + +Once the command is executed, you will be greeted with a screen similar to below (statistics shown will vary based on + the data you have): + +.Home page for application +image::UiHome.png[width="400"] + +The screen consists of a map showing your pending orders and a statistics panel. The size of the +placeholder is indicative of how many orders they are in a region. You can observe these +components change when carrying out some of the other commands in the application! (try `/order find`) + +This feature allows you to keep track of how many orders you have on certain days and also take note of +what is the most common item people have ordered from your stall! +// end::home[] + +// tag::orderAdd[] +=== Adding an order : `/order add` `[Since v1.1]` + +Allows you to adds a new order to the list of orders. + +Format: `/order add f/FOOD n/NAME p/PHONE a/ADDRESS dt/DATETIME` + +There are also some things you need to take note of: +**** +* Able to add more than 1 food items by specifying more food tags. e.g. `f/Roti Prata f/Ice Milo`. +* Address must be a valid address that exists in Singapore to be displayed on the map. +* If the postal code is longer than 6 digits, much like any other field, only the *last 6 digits* will be taken into +consideration for approximating the location +* The manager is able to add dates from any time period as long as it is valid. +* Two orders are considered the same if they have the same name, phone, and date time. **** -* 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, ... + +Examples of add order commands (note that the above parameters can be re-ordered): + +* `/order add f/Roti Prata n/John Smith p/12345678 a/block 123, Clementi Drive, #01-01 610123 dt/26-12-2018 10:10:54` +* `/order add a/block 55 Computing Drive 117417 n/Harry f/Tissue Prata f/Nasi Briyani p/81889111 dt/14-12-2018 14:10:54` +// end::orderAdd[] + +// tag::orderList[] +=== Listing all orders : `/order list` `[Since v1.1]` + +Shows a list of all orders in chronological order + +Format: `/order list` +// end::orderList[] + +// tag::orderedit[] +=== Editing an order : `/order edit` `[Since v1.2]` + +Edits an existing order in the list of orders. + +Format: `/order edit INDEX [f/FOOD] [n/NAME] [p/PHONE] [a/ADDRESS] [dt/DATETIME]` + +**** +* Edits the order at the specified `INDEX`. * 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 a parameter is specified, e.g. `f/`, `n/`, empty fields are not allowed. Value must be specified. +* Orders that are `Ongoing` or `Completed` cannot be edited. **** 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. +* `/order list` + +`/order edit 1 p/91234567 n/Jonathan` + +Edits the phone number and name of the 1st order to be `91234567` and `Jonathan` respectively. +* `/order list` + +`/order edit 2 f/Maggi Goreng f/Ice Milo` + +Edits the food of the 2nd order to be `Maggi Goreng, Ice Milo`. +// end::orderedit[] -=== Locating persons by name: `find` +// tag::orderfind[] +=== Finding orders : `/order find` `[Since v1.2]` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Find any order/s with any given order fields. + +Format: `/order find [n/NAME] [p/PHONE] [a/ADDRESS] [f/FOOD] [dt/DATETIME] [st/ORDER_STATUS]` + **** -* 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` +* All fields are optional. However, at least one field must be specified. +* Empty value after any fields are not allowed. e.g. `n/` +* The search is case insensitive. e.g `tom` will match `Tom` +* Partial match is allowed for name, phone, food and address. e.g. `alex` will match `alex lim` and `al` will match `alex lim` +* When more than 1 fields are given, the find will do an `and` on all fields. e.g. `n/alex p/1234`, will be `n == 'alex' AND p == 1234` +* When the same fields are given, it will take the last field, except for date search. For date search refer below. e.g. `n/alex n/tom`, Only `n/tom` is taken +* Phone Find +** Multiple phone search is allow when phone numbers are separated by spaces. e.g. `p/1234 9111` will match phones with `1234` and `9111` +* Date Find +** To find for orders from a specific date, use 1 date field. e.g `dt/01-10-2018 10:00:00` will return order/s on that + date. +** To find for orders with a date range, use 2 or more date fields. e.g. `dt/01-10-2018 10:00:00 dt/03-10-2018 + 10:00:00` will return order/s within the 2 dates. When more than 2 date fields are given, it will take the smallest and the biggest datetime **** Examples: -* `find John` + +* `/order find n/john` + Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `/order find p/81231233 a/block 123, Clementi Drive, #01-01` + +Returns orders with phone number of `81231233` and address of `block 123, +Clementi Drive, #01-01` +* `/order find dt/01-10-2018 10:00:00 dt/03-10-2018 10:00:00` + +Returns for orders that are within the datetime `01-10-2018 10:00:00 and 03-10-2018 10:00:00` +// end::orderfind[] -=== Deleting a person : `delete` +=== Deleting an order : `/order delete` `[Since v1.1]` -Deletes the specified person from the address book. + -Format: `delete INDEX` +Deletes the specified order from the list of order. + +Format: `/order delete INDEX` **** -* Deletes 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, ... +* Deletes an order at the specified `INDEX`. +* Orders that are `Ongoing` cannot be deleted. **** 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. +* `/order list` + +`/order delete 2` + +Deletes the 2nd order in the list of orders. +* `/order find n/tom` + +`/order delete 1` + +Deletes the 1st order in the results of the `find` command. -=== Selecting a person : `select` +=== Selecting an order : `/order select` `[Since v1.1]` -Selects the person identified by the index number used in the displayed person list. + -Format: `select INDEX` +Selects the specified order from the list of order. + +Format: `/order select INDEX` **** -* Selects the person and loads the Google search page 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, ...` +* Select an order at the specified `INDEX`. +* Renders a new view for the select order - use /home to return the original view **** Examples: -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +* `/order list` + +`/order select 2` + +Selects the 2nd order in the list of orders. +* `/order find n/david` + +`/order select 1` + +Selects the 1st order in the results of the `find` command. -=== Listing entered commands : `history` +=== Clearing all order entries : `/order clear` `[Since v1.1]` -Lists all the commands that you have entered in reverse chronological order. + -Format: `history` +Clears all entries from the list of orders. + +Format: `/order clear` -[NOTE] -==== -Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. -==== +**** +* If there is an order already assigned to a deliveryman, order list cannot be cleared. +**** -// tag::undoredo[] -=== Undoing previous command : `undo` +// tag::deliverymanadd[] +=== Adding a delivery man : `/deliveryman add` `[Since v1.2]` -Restores the address book to the state before the previous _undoable_ command was executed. + -Format: `undo` +Allows you to add a delivery man to the list of delivery men + +Format: `/deliveryman add n/NAME` -[NOTE] -==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). -==== +Examples: + +* `/deliveryman add n/John Smith` +// end::deliverymanadd[] + +// tag::deliverymanlist[] +=== Listing all delivery men : `/deliveryman list` `[Since v1.2]` + +Lets you see the list of all delivery men in that were added for chronological order + +Format: `/deliveryman list` +// end::deliverymanlist[] + +=== Editing a delivery man : `/deliveryman edit` `[Coming in v2.0]` + +Edits an existing delivery man in the list of delivery men. + +Format: `/deliveryman edit INDEX n/NAME` + +**** +* Edits the delivery man at the specified `INDEX`. +* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +* When a parameter is specified, e.g. `n/`, empty fields are not allowed. A value must be specified. +**** Examples: -* `delete 1` + -`list` + -`undo` (reverses the `delete 1` command) + +* `/deliveryman list` + +`/deliveryman edit 1 n/John Doe` + +Edits the name of the 1st delivery man to be `John Doe`. -* `select 1` + -`list` + -`undo` + -The `undo` command fails as there are no undoable commands executed previously. +// tag::deliverymanfind[] +=== Finding delivery man by name : `/deliveryman find` `[Since v1.2]` -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +Find delivery men whose name contain in the given parameter. + +Format: `/deliveryman find n/NAME` + -=== Redoing the previously undone command : `redo` +**** +* The search is case insensitive. e.g `tom` will match `Tom` +* When more than one name field is given, it will only consider the last name field. e.g. `n/alex n/tom`, only `n/tom` will be considered +* Partial match is allowed e.g. `tom` will match `tommy` +**** + +Examples: + +* `/deliveryman find n/John Smith` + +Returns all delivery men with the names `John` e.g. `John Smith` and `John Doe` +// end::deliverymanfind[] -Reverses the most recent `undo` command. + -Format: `redo` +// tag::deliverymandelete[] +=== Deleting a delivery man : `/deliveryman delete` `[Since v1.2]` + +Allows you to delete the specified delivery man from the list of delivery men + +Format: `/deliveryman delete INDEX` + +**** +* Deletes a delivery man at the specified `INDEX`. +* Deliveryman that already have orders assigned cannot be deleted. +**** Examples: -* `delete 1` + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +* `/deliveryman list` + +`/deliveryman delete 2` + +Deletes the 2nd delivery man in the list of delivery men. +* `/deliveryman find n/tom` + +`/deliveryman delete 1` + +Deletes the 1st delivery man in the results of the `find` command. +// end::deliverymandelete[] + +// tag::deliverymanselect[] +=== Selecting a deliveryman : `/deliveryman select` `[Since v1.4]` + +Allows you to select the specified deliveryman from the list of deliverymen. + +Format: `/deliveryman select INDEX` + +**** +* Select an deliveryman at the specified `INDEX`. +* Renders a new view for the selected deliveryman - use /home to return the original view +**** + +Examples: + +* `/deliveryman list` + +`/deliveryman select 2` + +Selects the 2nd deliveryman in the list of deliverymen. +* `/deliveryman find n/david` + +`/deliveryman select 1` + +Selects the 1st deliveryman in the results of the `find` command. +// end::deliverymanselect[] + +=== Clearing all delivery men entries : `/deliveryman clear` `[Coming in v2.0]` + +Allows you to clear all entries from the list of delivery men. + +Format: `/deliveryman clear` + +// tag::assignfeature[] +=== Assign orders to a delivery man : `/assign` `[Since v1.3]` + +Lets you assign multiple orders to a delivery man + +Format: `/assign d/DELIVERYMAN_INDEX o/ORDER_INDEX` + +**** +* Assigns orders at the specific `ORDER_INDEX` to the delivery man at the `DELIVERYMAN_INDEX` +* There must be at least 1 order and 1 delivery man. +* Add more than 1 orders by specifying more tags. e.g. `o/1 o/2 o/3`. +* Orders that are `Ongoing` or `Completed` cannot be reassigned. +* No more than 5 can be assigned to a deliveryman at one time. +**** + +Examples: + +* `/order list` + +`/deliveryman list` + +`/assign d/2 o/1 o/2` + +Assigns order number 1 and 2 to delivery man number 2. + +Before the command is executed, the order status is `Pending` and the delivery man is available: + +.Before `/assign` command is executed +image::assign-before.png[width="750"] -* `delete 1` + -`redo` + -The `redo` command fails as there are no `undo` commands executed previously. +After the command is executed, the order status changes to `Ongoing` and the delivery man has 2 assigned orders: -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + -`redo` (reapplies the `clear` command) + -// end::undoredo[] +.After `/assign` command is executed successfully +image::assign-after.png[width="750"] -=== Clearing all entries : `clear` +// end::assignfeature[] -Clears all entries from the address book. + -Format: `clear` +// tag::orderdone[] +=== Mark an order as completed : `/order done` `[Since v1.4]` -=== Exiting the program : `exit` +Mark an order as completed. +Format: `/order done ORDER_INDEX` + +**** +* Mark ORDER_INDEX as completed. +* The index refers to the index number shown in the orders list. +* The index *must be a positive integer* 1, 2, 3, ... and must be within the number of displayed orders. +* There must be only 1 order index. +* Only `ongoing` orders can be marked as completed. +**** + +Examples: + +* `/order list` + +`/deliveryman list` + +`/assign d/2 o/1` + +`/order done 1` +Mark order number 1 as completed. + +After you mark an order done: + +.Order status changed to Completed after the order is marked as done +image::orderdone.png[width="350"] + +// end::orderdone[] + +=== Listing entered commands : `/history` `[Since v1.1]` + +Lists all the commands that you have entered in reverse chronological order. + +Format: `/history` + +[NOTE] +==== +Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. +==== + +=== Exiting the program : `/exit` `[Since v1.1]` Exits the program. + -Format: `exit` +Format: `/exit` + +// tag::routecreatefeature[] +=== Creating a new route: `/route create` `[Deprecated since v1.2.1]` + +Creates a route with a set of orders + +Format: `/route create o/ORDER_ID` + +**** +* All fields need to have at least a value. e.g. `o/` is not allowed. +* Able to add more than 1 orders by specifying more tags. e.g. `o/1 o/2 o/3`. +**** + +Examples: + +* `/route create o/1 o/3` +// end::routecreatefeature[] === Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +FoodZoom data are saved in the hard disk automatically after any command that changes the data. + There is no need to save manually. -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +=== Viewing help : `/help` `(Deliveryman Command)` `[Coming in v2.0]` + +Format: `/help` + +=== Login to system : `/login` `(Deliveryman Command)` `[Coming in v2.0]` + +Format: `/login u/USERNAME pw/PASSWORD` -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +Examples: + +* `/login u/deliveryman pw/password` + +=== Logout from system : `/logout` `(Deliveryman Command)` `[Coming in v2.0]` + +Format: `/logout` + +=== Listing assigned orders : `/order list` `(Deliveryman Command)` `[Coming in v2.0]` + +List details of assigned order/s + +Format: `/order list` + +=== Exiting the program : `/exit` `(Deliveryman Command)` `[Coming in v2.0]` + +Exits the program. + +Format: `/exit` == FAQ @@ -242,19 +566,50 @@ _{explain how the user can enable/disable data encryption}_ == 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` -* *Select* : `select INDEX` + -e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +* *Sign Up* : `/signup n/NAME u/USERNAME pw/PASSWORD` + +e.g. `/signup n/John Doe u/johndoe pw/johndoepassword` +* *Login* : `/login u/USERNAME pw/PASSWORD` + +e.g. `/login u/manager pw/password` +* *Logout* : `/logout` +* *Return to Home* : `/home` +* *Add order* : `/order add f/FOOD n/NAME p/PHONE a/ADDRESS dt/DATETIME` + +e.g. `/order add f/Roti Prata n/James Ho p/22224444 a/block 123, Clementi Rd, 1234665 dt/14-12-2018 10:18:00` +* *Listing orders* : `/order list` +* *Edit order* : `/order edit INDEX [f/FOOD] [n/NAME] [p/PHONE] [a/ADDRESS] [dt/DATETIME]` + +e.g. `/order edit 2 n/James Lee` +* *Find order* : `/order find [n/NAME] [p/PHONE] [a/ADDRESS] [f/FOOD] [dt/DATETIME] [st/ORDER_STATUS]` + +e.g. `/order find n/James Jake` +* *Delete order* : `/order delete INDEX` + +e.g. `/order delete 3` +* *Select order* : `/order select INDEX` + +e.g. `/order select 3` +* *Clear order* : `/order clear` +* *Add delivery man* : `/deliveryman add n/NAME` + +e.g. `/deliveryman add n/James Ho` +* *Listing delivery men* : `/deliveryman list` +* *Edit delivery man* (Coming in v2.0) : `/deliveryman edit INDEX n/NAME` + +e.g. `/deliveryman edit 2 n/James Lee` +* *Find delivery man* : `/deliveryman find n/NAME` + +e.g. `/deliveryman find n/James Jake` +* *Delete delivery man* : `/deliveryman delete INDEX` + +e.g. `/deliveryman delete 3` +* *Select delivery man* : `/deliveryman select INDEX` + +e.g. `/deliveryman select 1` +* *Clear delivery men* (Coming in v2.0) : `/deliveryman clear` +* *Assign orders to a delivery man* : `/assign d/DELIVERYMAN_INDEX o/ORDER_INDEX` + +e.g. `/assign d/1 o/1 o/3` +* *Mark an order as completed* : `/order done ORDER_INDEX` + +e.g. `/order done 1` +* *Help* : `/help` +* *History* : `/history` +* *Exit the program* : `/exit` +* *Create a route* [DEPRECATED] : `/route create o/ORDER_INDEX` + +e.g. `/route create o/1 o/3` + +* *Login (Deliveryman)* (Coming in v2.0) : `/login u/USERNAME pw/PASSWORD` + +e.g. `/login u/deliveryman pw/password` +* *Logout (Deliveryman)* (Coming in v2.0) : `/logout` +* *View delivery route (Deliveryman)* (Coming in v2.0) : `/route list` +* *View assigned orders (Deliveryman)* (Coming in v2.0) : `/order list` +* *Help (Deliveryman)* (Coming in v2.0) : `/help` +* *Exit the program (Deliveryman)* (Coming in v2.0) : `/exit` diff --git a/docs/diagrams/HighLevelSequenceDiagrams.pptx b/docs/diagrams/HighLevelSequenceDiagrams.pptx index 38332090a79a..d1f122a7ca83 100644 Binary files a/docs/diagrams/HighLevelSequenceDiagrams.pptx and b/docs/diagrams/HighLevelSequenceDiagrams.pptx differ diff --git a/docs/diagrams/LogicComponentClassDiagram.pptx b/docs/diagrams/LogicComponentClassDiagram.pptx index 6fcc1136a5bb..709f7f30a417 100644 Binary files a/docs/diagrams/LogicComponentClassDiagram.pptx and b/docs/diagrams/LogicComponentClassDiagram.pptx differ diff --git a/docs/diagrams/LogicComponentSequenceDiagram.pptx b/docs/diagrams/LogicComponentSequenceDiagram.pptx index c5b6d5fad6e3..7b2831090a9a 100644 Binary files a/docs/diagrams/LogicComponentSequenceDiagram.pptx and b/docs/diagrams/LogicComponentSequenceDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index 3c976908eaa7..86e47a95417f 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/OrderAddCommandSequenceDiagram.pptx b/docs/diagrams/OrderAddCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..24f2d1402f71 Binary files /dev/null and b/docs/diagrams/OrderAddCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/OrderFindCommandSequenceDiagram.pptx b/docs/diagrams/OrderFindCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..109e219806af Binary files /dev/null and b/docs/diagrams/OrderFindCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..ed91cfbf83ff 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/diagrams/StorageSequenceDiagram.pptx b/docs/diagrams/StorageSequenceDiagram.pptx new file mode 100644 index 000000000000..0b71b88ee71d Binary files /dev/null and b/docs/diagrams/StorageSequenceDiagram.pptx differ diff --git a/docs/diagrams/UIListenerActivityDiagram.pptx b/docs/diagrams/UIListenerActivityDiagram.pptx new file mode 100644 index 000000000000..ac4d608a1641 Binary files /dev/null and b/docs/diagrams/UIListenerActivityDiagram.pptx differ diff --git a/docs/diagrams/UiComponentClassDiagram.pptx b/docs/diagrams/UiComponentClassDiagram.pptx index 384d0a00e6ea..7f4b29a5d5fa 100644 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoActivityDiagram.pptx b/docs/diagrams/UndoRedoActivityDiagram.pptx index 16fec930cf3f..157d6e6bc3cc 100644 Binary files a/docs/diagrams/UndoRedoActivityDiagram.pptx and b/docs/diagrams/UndoRedoActivityDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx b/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx index 6fd31b5f3fbd..c2f2fecf98fe 100644 Binary files a/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx and b/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx index 1f3261976dce..d06e82378500 100644 Binary files a/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx index e2907d4a9cae..d1ee4153252c 100644 Binary files a/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx index 4ecc659bd600..ed43815b0136 100644 Binary files a/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx index 16ebf585ddbd..f87ba16da967 100644 Binary files a/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoSequenceDiagram.pptx b/docs/diagrams/UndoRedoSequenceDiagram.pptx index 5ccc1042caac..14923abff456 100644 Binary files a/docs/diagrams/UndoRedoSequenceDiagram.pptx and b/docs/diagrams/UndoRedoSequenceDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoStartingStateListDiagram.pptx b/docs/diagrams/UndoRedoStartingStateListDiagram.pptx index 98ce067642ff..52582d3d32b6 100644 Binary files a/docs/diagrams/UndoRedoStartingStateListDiagram.pptx and b/docs/diagrams/UndoRedoStartingStateListDiagram.pptx differ diff --git a/docs/images/AssignCommandSequenceDiagram1.png b/docs/images/AssignCommandSequenceDiagram1.png new file mode 100644 index 000000000000..a892088925b6 Binary files /dev/null and b/docs/images/AssignCommandSequenceDiagram1.png differ diff --git a/docs/images/AssignCommandSequenceDiagram2.png b/docs/images/AssignCommandSequenceDiagram2.png new file mode 100644 index 000000000000..9ba8486b96d9 Binary files /dev/null and b/docs/images/AssignCommandSequenceDiagram2.png differ diff --git a/docs/images/CreateRouteCommandSequenceDiagram1.png b/docs/images/CreateRouteCommandSequenceDiagram1.png new file mode 100644 index 000000000000..b59dea78e5e6 Binary files /dev/null and b/docs/images/CreateRouteCommandSequenceDiagram1.png differ diff --git a/docs/images/CreateRouteCommandSequenceDiagram2.png b/docs/images/CreateRouteCommandSequenceDiagram2.png new file mode 100644 index 000000000000..f3c5a36e7529 Binary files /dev/null and b/docs/images/CreateRouteCommandSequenceDiagram2.png differ diff --git a/docs/images/DeleteOrderSdForLogic.png b/docs/images/DeleteOrderSdForLogic.png new file mode 100644 index 000000000000..01f8dd7a3bc8 Binary files /dev/null and b/docs/images/DeleteOrderSdForLogic.png differ diff --git a/docs/images/DeliverymanAddCommandSequenceDiagram.png b/docs/images/DeliverymanAddCommandSequenceDiagram.png new file mode 100644 index 000000000000..5a697408d939 Binary files /dev/null and b/docs/images/DeliverymanAddCommandSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index f4ecf65b3193..f5608ab5c5f6 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/LoginCommandSequenceDiagram1.png b/docs/images/LoginCommandSequenceDiagram1.png new file mode 100644 index 000000000000..21ddcdba928a Binary files /dev/null and b/docs/images/LoginCommandSequenceDiagram1.png differ diff --git a/docs/images/LoginCommandSequenceDiagram2.png b/docs/images/LoginCommandSequenceDiagram2.png new file mode 100644 index 000000000000..0887bbdaee0f Binary files /dev/null and b/docs/images/LoginCommandSequenceDiagram2.png differ diff --git a/docs/images/MapPanel.png b/docs/images/MapPanel.png new file mode 100644 index 000000000000..82a275f1a66c Binary files /dev/null and b/docs/images/MapPanel.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 9fb19078b859..49d715ecc78e 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/OrderAddSequenceDiagram.png b/docs/images/OrderAddSequenceDiagram.png new file mode 100644 index 000000000000..36e1fdfd19c4 Binary files /dev/null and b/docs/images/OrderAddSequenceDiagram.png differ diff --git a/docs/images/OrderDatePredicate.png b/docs/images/OrderDatePredicate.png new file mode 100644 index 000000000000..323d6f44b41f Binary files /dev/null and b/docs/images/OrderDatePredicate.png differ diff --git a/docs/images/OrderFindSequenceDiagram.png b/docs/images/OrderFindSequenceDiagram.png new file mode 100644 index 000000000000..af26cd6c4330 Binary files /dev/null and b/docs/images/OrderFindSequenceDiagram.png differ diff --git a/docs/images/SDforDeleteOrder.png b/docs/images/SDforDeleteOrder.png new file mode 100644 index 000000000000..509631fe1abe Binary files /dev/null and b/docs/images/SDforDeleteOrder.png differ diff --git a/docs/images/SDforDeleteOrderEventHandling.png b/docs/images/SDforDeleteOrderEventHandling.png new file mode 100644 index 000000000000..6672057430b6 Binary files /dev/null and b/docs/images/SDforDeleteOrderEventHandling.png differ diff --git a/docs/images/SingaporeDemarcation.png b/docs/images/SingaporeDemarcation.png new file mode 100644 index 000000000000..eeec1b203ef9 Binary files /dev/null and b/docs/images/SingaporeDemarcation.png differ diff --git a/docs/images/StatisticsPanel.png b/docs/images/StatisticsPanel.png new file mode 100644 index 000000000000..ccccded49895 Binary files /dev/null and b/docs/images/StatisticsPanel.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..97b6c8257774 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/StorageSequenceDiagram.png b/docs/images/StorageSequenceDiagram.png new file mode 100644 index 000000000000..e9ec1c772e96 Binary files /dev/null and b/docs/images/StorageSequenceDiagram.png differ diff --git a/docs/images/UIListenerActivityDiagram.png b/docs/images/UIListenerActivityDiagram.png new file mode 100644 index 000000000000..f3450f312604 Binary files /dev/null and b/docs/images/UIListenerActivityDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..d0f577af273d 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram1.png b/docs/images/UiClassDiagram1.png new file mode 100644 index 000000000000..6747c4c80166 Binary files /dev/null and b/docs/images/UiClassDiagram1.png differ diff --git a/docs/images/UiComponents.png b/docs/images/UiComponents.png new file mode 100644 index 000000000000..00d6a4bc8e93 Binary files /dev/null and b/docs/images/UiComponents.png differ diff --git a/docs/images/UiHome.png b/docs/images/UiHome.png new file mode 100644 index 000000000000..c91584d0f0fe Binary files /dev/null and b/docs/images/UiHome.png differ diff --git a/docs/images/UndoRedoActivityDiagram.png b/docs/images/UndoRedoActivityDiagram.png index 55e4138cc64f..1c4e46b167b5 100644 Binary files a/docs/images/UndoRedoActivityDiagram.png and b/docs/images/UndoRedoActivityDiagram.png differ diff --git a/docs/images/UndoRedoExecuteUndoStateListDiagram.png b/docs/images/UndoRedoExecuteUndoStateListDiagram.png index 29c365d6b4a1..a993bc8f69fe 100644 Binary files a/docs/images/UndoRedoExecuteUndoStateListDiagram.png and b/docs/images/UndoRedoExecuteUndoStateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand1StateListDiagram.png b/docs/images/UndoRedoNewCommand1StateListDiagram.png index 76e661d62027..1028262c42bb 100644 Binary files a/docs/images/UndoRedoNewCommand1StateListDiagram.png and b/docs/images/UndoRedoNewCommand1StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand2StateListDiagram.png b/docs/images/UndoRedoNewCommand2StateListDiagram.png index adcb9aeadc51..2a958ac3afe3 100644 Binary files a/docs/images/UndoRedoNewCommand2StateListDiagram.png and b/docs/images/UndoRedoNewCommand2StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand3StateListDiagram.png b/docs/images/UndoRedoNewCommand3StateListDiagram.png index aac9c5fe05db..4c7f588b1f8f 100644 Binary files a/docs/images/UndoRedoNewCommand3StateListDiagram.png and b/docs/images/UndoRedoNewCommand3StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand4StateListDiagram.png b/docs/images/UndoRedoNewCommand4StateListDiagram.png index 66a0a3b5f323..8cc4ef228f84 100644 Binary files a/docs/images/UndoRedoNewCommand4StateListDiagram.png and b/docs/images/UndoRedoNewCommand4StateListDiagram.png differ diff --git a/docs/images/UndoRedoSequenceDiagram.png b/docs/images/UndoRedoSequenceDiagram.png index 5c9d5936f098..94f1e7f6cd9a 100644 Binary files a/docs/images/UndoRedoSequenceDiagram.png and b/docs/images/UndoRedoSequenceDiagram.png differ diff --git a/docs/images/UndoRedoStartingStateListDiagram.png b/docs/images/UndoRedoStartingStateListDiagram.png index 002f3e2bbf79..1f1fbb40a7e8 100644 Binary files a/docs/images/UndoRedoStartingStateListDiagram.png and b/docs/images/UndoRedoStartingStateListDiagram.png differ diff --git a/docs/images/assign-after.png b/docs/images/assign-after.png new file mode 100644 index 000000000000..8ce03ee5015a Binary files /dev/null and b/docs/images/assign-after.png differ diff --git a/docs/images/assign-before.png b/docs/images/assign-before.png new file mode 100644 index 000000000000..f25c16f89ff0 Binary files /dev/null and b/docs/images/assign-before.png differ diff --git a/docs/images/damithc.jpg b/docs/images/damithc.jpg deleted file mode 100644 index 127543883893..000000000000 Binary files a/docs/images/damithc.jpg and /dev/null differ diff --git a/docs/images/jinyingtan.png b/docs/images/jinyingtan.png new file mode 100644 index 000000000000..b1fa1a52419f Binary files /dev/null and b/docs/images/jinyingtan.png differ diff --git a/docs/images/juxd.png b/docs/images/juxd.png new file mode 100644 index 000000000000..c727880bc85f Binary files /dev/null and b/docs/images/juxd.png differ diff --git a/docs/images/kohchihao.png b/docs/images/kohchihao.png new file mode 100644 index 000000000000..51b77b87e912 Binary files /dev/null and b/docs/images/kohchihao.png differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/monmanuela.png b/docs/images/monmanuela.png new file mode 100644 index 000000000000..fe03a81e7e29 Binary files /dev/null and b/docs/images/monmanuela.png differ diff --git a/docs/images/orderdone.png b/docs/images/orderdone.png new file mode 100644 index 000000000000..eee119a01a08 Binary files /dev/null and b/docs/images/orderdone.png differ diff --git a/docs/images/rrtheonlyone.png b/docs/images/rrtheonlyone.png new file mode 100644 index 000000000000..b68c917fe08e Binary files /dev/null and b/docs/images/rrtheonlyone.png differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a732272..000000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/stylesheets/asciidoctor.css b/docs/stylesheets/asciidoctor.css index 36590bf346cd..cfcfd058c5e7 100644 --- a/docs/stylesheets/asciidoctor.css +++ b/docs/stylesheets/asciidoctor.css @@ -405,3 +405,8 @@ body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-b .print-only{display:block!important} .hide-for-print{display:none!important} .show-for-print{display:inherit!important}} +@media print { + a[href]:after { + content: none !important; + } +} diff --git a/docs/team/jinyingtan.adoc b/docs/team/jinyingtan.adoc new file mode 100644 index 000000000000..b112d7110039 --- /dev/null +++ b/docs/team/jinyingtan.adoc @@ -0,0 +1,61 @@ += Tan Jin Ying - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +My name is Jin Ying, a Year 2 Student reading Computer Science at National University of Singapore. This +project portfolio documents my contributions to the project. + +== Project: FoodZoom + +--- + +== Overview +image::Ui.png[width="800"] + +FoodZoom is a desktop application providing food businesses a delivery management platform for them to streamline their delivery process. FoodZooms' responsiveness and quickness allows manager to plan for route through a Command Line Interface (CLI). The platform also allows delivery man to view their delivery routes and increase their efficiency. FoodZoom is created by 5 students, namely Tan Jin Ying, Monika Manuela, Julius Sander, Rahul Rajesh and Koh Chi Hao. + +== Summary of Contribution + +--- +* *Major enhancement*: +** Added the ability to *find for existing order/s* +*** What it does: It allows the user to find for existing order in the list of orders with any order parameter that it was created with. +*** Justification: This feature improve the ease for the user to look for the order in the potential long list of order they could have. +*** Highlights: The enhancement changes the `INDEX` in the User Interface. Any command that relies on the `INDEX` needs keep this in mind. + +** Added the ability to *edit existing order* +*** What it does: This feature allows the user to make amendments to any order they have created previous. +*** Justification: As a user could add the order with the wrong parameter, this feature allows the user to make any edits to the order. +*** Highlights: This enhancement creates a new `Order` object and any future commands that need a reference to the original `Order` object need to be able to track a change in the object. + +* *Minor enhancement*: added a find command to search for existing deliveryman + +* *Code Contribution*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=jinyingtan[RepoSense] + +* *Other contributions*: + +** Documentation: +*** Update User Guide for `v1.1` & `v1.2` (Pull requests https://github.com/CS2103-AY1819S1-T16-3/main/pull/27[#27], https://github.com/CS2103-AY1819S1-T16-3/main/pull/80[#80]) +** Community: +*** Review weekly Pull Requests +*** Reported bugs and suggestions for other projects (https://github.com/CS2103-AY1819S1-F10-1/main/issues/150[#150], https://github.com/CS2103-AY1819S1-F10-1/main/issues/153[#153], https://github.com/CS2103-AY1819S1-F10-1/main/issues/156[#156], https://github.com/CS2103-AY1819S1-F10-1/main/issues/158[#158], https://github.com/CS2103-AY1819S1-F10-1/main/issues/160[#160], https://github.com/CS2103-AY1819S1-F10-1/main/issues/164[#164], https://github.com/CS2103-AY1819S1-F10-1/main/issues/169[#169]) + +== Contributions to User Guide + +|=== +|This section shows my contributions to User Guide for the project. It showcases my ability to write user-centric documentation. +|=== + +include::../UserGuide.adoc[tag=orderfind] + +include::../UserGuide.adoc[tag=orderedit] + +include::../UserGuide.adoc[tag=deliverymanfind] + +== Contributions to Developer Guide + +|=== +|This section shows my contributions to Developer Guide for the project. It showcases my ability to write a technical document and the technical aspect of my contribution to the project. +|=== + +include::../DeveloperGuide.adoc[tag=orderfindfeature] diff --git a/docs/team/juxd.adoc b/docs/team/juxd.adoc new file mode 100644 index 000000000000..a1a272186104 --- /dev/null +++ b/docs/team/juxd.adoc @@ -0,0 +1,107 @@ += Julius Sander - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: FoodZoom + +--- +Hello there! My name is Julius Sander, and I am a Year 2 Computer Science student +in National University of Singapore. This is a report of my contributions to my most +recent team project, FoodZoom. + +== Overview + +FoodZoom is a desktop food delivery management application designed for +food businesses to streamline their delivery process. FoodZoom is +responsive and quick, allowing managers to plan routes for order +delivery and dispatch them to delivery men using the Command Line +Interface (CLI). The delivery men can also use this application to view +their delivery routes and increase their efficiency. FoodZoom is built +by 5 computer science student namely Rahul Rajesh, Julius Sander, Monika +Monuela, Koh Chi Hao and Tan Jin Ying. + +FoodZoom’s core functionality includes: + +* Users being able to track food orders (visualise on a map) they have from customers +* Managing deliverymen and assigning orders to them +* Analysis of orders to display relevant statistics (order history, trending food etc.) to users + + +.FoodZoom Home page +image::Ui.png[width="600"] + +== Summary of Contributions + +--- +|=== +|Given below are contributions that I made to this application. +|=== + +* *Major enhancement*: Added deliverymen into the application. + +** _What it does_: Managers can add, delete or assign order deliveries +to the deliverymen with this feature. Managers can also see and monitor +the activity of the deliverymen from the main screen. + +** _Justification_: This feature provides managers the ability to manage +deliveryman independently from orders, giving them added control over +their order deliveries. + +** _Highlights_: This enhancement adds new commands into the +application, and requires in-depth analysis of design analysis with +regards to information storage. + +* *Minor enhancement*: Added IDs to resources for storage. This provides +the application an ability to save and load complex associations between +resources in the application. + +* *Code contribution*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=juxd[RepoSense] + +* *Other contributions*: +** _Project Management_: +*** Manual & automated testing of application on major features +** _Community_: +*** Regular Pull Request reviews +(https://github.com/CS2103-AY1819S1-T16-3/main/pulls?utf8=%E2%9C%93&q=is%3Apr+reviewed-by%3Ajuxd[see here]) +*** Extensive reviews of major features with non-trivial comments + (https://github.com/CS2103-AY1819S1-T16-3/main/pull/131[#131], + https://github.com/CS2103-AY1819S1-T16-3/main/pull/50[#50]) +*** Reported bugs and gave suggestions for other group's project ( +Issue https://github.com/CS2103-AY1819S1-W12-4/main/issues/136[#136], +Issue https://github.com/CS2103-AY1819S1-W12-4/main/issues/134[#134], +Issue https://github.com/CS2103-AY1819S1-W12-4/main/issues/130[#130], +Issue https://github.com/CS2103-AY1819S1-W12-4/main/issues/126[#126], +Issue https://github.com/CS2103-AY1819S1-W12-4/main/issues/123[#123], +Issue https://github.com/CS2103-AY1819S1-W12-4/main/issues/121[#121], +Issue https://github.com/CS2103-AY1819S1-W12-4/main/issues/118[#118], +Issue https://github.com/CS2103-AY1819S1-W12-4/main/issues/109[#109] +) +** _Tools_: +*** Integrated Github plugins (Coveralls, Travis) into the project, providing a seamless +process to integrate code changes. + +== Contributions to User Guide + +|=== +|Given below are some of my contributions to the User Guide. They showcase my ability to write clear + documentation targeting end-users. +|=== + +include::../UserGuide.adoc[tag=deliverymanadd] + +include::../UserGuide.adoc[tag=deliverymanlist] + +include::../UserGuide.adoc[tag=deliverymandelete] + +include::../UserGuide.adoc[tag=deliverymanselect] + +== 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=storagecomponent] + +include::../DeveloperGuide.adoc[tag=deliverymanaddfeature] diff --git a/docs/team/kohchihao.adoc b/docs/team/kohchihao.adoc new file mode 100644 index 000000000000..7e393ef2f5b7 --- /dev/null +++ b/docs/team/kohchihao.adoc @@ -0,0 +1,79 @@ += Koh Chi Hao - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== Project: FoodZoom + +--- + +Hello there! I am Koh Chi Hao. I am currently studying at National University of Singapore (NUS) in School of Computing and majoring in Computer Science. As of right now, I am studying my second year of study. This portfolio serves the purpose of documenting what I have contributed to the project and my roles in it. + +== Overview + +FoodZoom is a desktop food delivery management application designed for food businesses to streamline their delivery process. FoodZoom is responsive and quick, allowing managers to plan routes for order delivery and dispatch them to delivery men using the Command Line Interface (CLI). The delivery men can also use this application to view their delivery routes and increase their efficiency. FoodZoom is built by 5 computer science student namely Koh Chi Hao, Rahul Rajesh, Julius Sander, Monika Manuela Hengki and Tan Jin Ying. The picture below shows the home page of FoodZoom. + +image::Ui.png[width="800"] + + +== Summary of Contribution + +--- + +|=== +|Given below are sections I contributed to the application. They showcase my ability to write and manage the code within the project. +|=== + +* *Major enhancement*: added the ability to sign up, login and logout for the user. +** What it does: It allows the user the sign up for the application when they first use it. Subsequently, they can +login into the application to create orders and dispatch them. At the end of the day, they can easily log out from the application to protect the details. +** Justification: This feature improves the security of the application as it allows only the manager of the shop to +use the application and no one else. As such, the application will be protected against random people from the shop attempting to foil the data. +** Highlights: This enhancement will affect all the current and future commands because only certain commands can be +used when the user is not logged in. As such, when commands in the future are implemented, they are required to be place under either authorised or unauthorised commands. + +* *Minor enhancement*: +** Implemented major UI changes when user log in and log out. +** Updated status bar with username after the user log in. +** Add `/order done` command. + +* *Code contribution*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=kohchihao[RepoSense] + +* *Other contributions*: +** Project management: +*** Managed release for `v1.1`, `v1.2`, `v1.2.1`, `v1.3` +** Documentation: +*** Update DeveloperGuide to remove and update all non-relevant stuff (Pull requests https://github.com/CS2103-AY1819S1-T16-3/main/pull/90[#90]) +** Community: +*** Review weekly Pull Requests +*** Contributed to CS2103T forum discussion https://github.com/nus-cs2103-AY1819S1/forum/issues/created_by/kohchihao[(questions I posted)] +*** Reported bugs and suggestions for other projects ( + https://github.com/CS2103-AY1819S1-W10-4/main/issues/186[#186], + https://github.com/CS2103-AY1819S1-W10-4/main/issues/188[#188], + https://github.com/CS2103-AY1819S1-W10-4/main/issues/189[#189], + https://github.com/CS2103-AY1819S1-W10-4/main/issues/194[#194], + https://github.com/CS2103-AY1819S1-W10-4/main/issues/198[#198], + https://github.com/CS2103-AY1819S1-W10-4/main/issues/200[#200], + https://github.com/CS2103-AY1819S1-W10-4/main/issues/206[#206], + https://github.com/CS2103-AY1819S1-W10-4/main/issues/207[#207]) + +== Contributions to User Guide + +|=== +|This section shows my contributions to User Guide for the project. It showcases my ability to write user-centric documentation. +|=== + +include::../UserGuide.adoc[tag=signup] + +include::../UserGuide.adoc[tag=login] + +include::../UserGuide.adoc[tag=logout] + +include::../UserGuide.adoc[tag=orderdone] + +== Contributions to Developer Guide + +|=== +|This section shows my contributions to Developer Guide for the project. It showcases my ability to write a technical document and the technical aspect of my contribution to the project. +|=== + +include::../DeveloperGuide.adoc[tag=loginfeature] diff --git a/docs/team/monmanuela.adoc b/docs/team/monmanuela.adoc new file mode 100644 index 000000000000..255fcd417ce2 --- /dev/null +++ b/docs/team/monmanuela.adoc @@ -0,0 +1,90 @@ += Monika Manuela Hengki - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== Project: FoodZoom + +--- + +Hello there! I am Monika Manuela Hengki, a Computer Science student in National University of Singapore. I +like building useful things that can help improve other people’s lives. Below you can find some of my +contributions to one of my projects. + +== Overview + +FoodZoom is a food delivery management desktop application for food businesses to streamline their +delivery process. FoodZoom is responsive and quick as users interact with the application by typing the +commands (using Command Line Interface). FoodZoom allows managers to plan order delivery routes and +dispatch them to deliverymen available. Deliverymen can also quickly view their assigned delivery routes and +increase their efficiency. FoodZoom is built by me and 4 other Computer Science students. + +.FoodZoom Home Page +image::Ui.png[width="800"] + +== Summary of Contribution + +--- +|=== +|Given below are contributions that I made to this application. +|=== + +* *Major enhancement*: added the ability to assign orders to a deliveryman. + +** What it does: It allows the manager to assign multiple orders to a deliveryman. Subsequently, the +order status changes from `PENDING` to `ONGOING`, which means that it can no longer be edited or +reassigned to another deliveryman. + +** Justification: This feature streamlines the food delivery process as the manager can assign orders +to be delivered by their deliverymen conveniently. + +** Highlights: The implementation was challenging as it required two-way association between the order and the deliveryman. + +* *Minor enhancements*: + +** Improved checking for the date of the order to prevent invalid dates. +** Modified command words of some commands to unify the patterns. + +* *Code contributions*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=monmanuela[RepoSense] + +* *Other contributions*: + +** Project management: +*** Helped with the issue tracking using labels and assignees on Github. + +** Documentation: +*** Updated README file to add descriptions of the application with relevant pictures (Pull requests https://github.com/CS2103-AY1819S1-T16-3/main/pull/3[#3]) +*** Updated User Guide and Developer Guide for the features I added + +** Community: +*** Review pull requests weekly (https://github.com/CS2103-AY1819S1-T16-3/main/pulls?utf8=%E2%9C%93&q=is%3Apr+reviewed-by%3Amonmanuela[see here]) +*** Reported bugs and gave suggestions for other group's project ( +Issue https://github.com/CS2103-AY1819S1-W13-4/main/issues/172[#172], + https://github.com/CS2103-AY1819S1-W13-4/main/issues/173[#173], + https://github.com/CS2103-AY1819S1-W13-4/main/issues/174[#174], + https://github.com/CS2103-AY1819S1-W13-4/main/issues/175[#175], + https://github.com/CS2103-AY1819S1-W13-4/main/issues/176[#176], + https://github.com/CS2103-AY1819S1-W13-4/main/issues/178[#178], + https://github.com/CS2103-AY1819S1-W13-4/main/issues/180[#180], + https://github.com/CS2103-AY1819S1-W13-4/main/issues/185[#185], + https://github.com/CS2103-AY1819S1-W13-4/main/issues/188[#188], + https://github.com/CS2103-AY1819S1-W13-4/main/issues/191[#191]) + +== Contributions to User Guide +|=== +|Given below are some of my contributions to the User Guide. They showcase my ability to write clear + documentation targeting end-users. +|=== + +include::../UserGuide.adoc[tag=assignfeature] + +include::../UserGuide.adoc[tag=routecreatefeature] + +== Contributions to Developer Guide +|=== +|Given below are some of my contributions 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=assign] + +include::../DeveloperGuide.adoc[tag=route] diff --git a/docs/team/rrtheonlyone.adoc b/docs/team/rrtheonlyone.adoc new file mode 100644 index 000000000000..c72d1f150af4 --- /dev/null +++ b/docs/team/rrtheonlyone.adoc @@ -0,0 +1,121 @@ += Rahul Rajesh - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== Overview + +I am a year 2 Computer Science Student studying at National University of Singapore. I am +interested in using technology to solve everyday problems and am always on the lookout for +any challenging project to work with. This portfolio page serves to document my work on FoodZoom. + + +== Project: FoodZoom + +FoodZoom is a desktop application allowing users to streamline their delivery process. The application is +specifically targeted towards food businesses who need an efficient way to manage their delivery services. +FoodZoom uses a Command-Line Interface, meaning users can simply make use of keyword based commands when running the +application. The application was built by a team of 5 students including myself. + +FoodZoom's *core functionality* includes: + +- Users being able to track food orders (visualise on a map) they have from customers +- Managing deliverymen and assigning orders to them +- Analysis of orders to display relevant statistics (order history, trending food etc.) to users + +== Summary of Contributions + +--- +|=== +|The below sections showcase some of the contributions that I made to this application. +|=== +-- +* *Major enhancement:* Built the system that allows for the *generation of statistics* and *location-based matching* +based off the provided data (Pull Request +https://github.com/CS2103-AY1819S1-T16-3/main/pull/102[#102], +https://github.com/CS2103-AY1819S1-T16-3/main/pull/121[#121]) + +** What it does: The main screen consists of a map and a dashboard. The map is able to show the number of pending +orders at a region. The dashboard shows statistics like the most popular food item and the order history + +** Justification: Users of this application can track where their pending orders are in one glance. Orders in the +same region can now be dispatched to the same deliveryman. Users can also use the generated statistics to improve their +profits and improve the overall experience they can provide to their customers + +* *Major enhancement:* Completely *revamped* and *redesigned* the User Interface (UI) for the application +(Pull Request +https://github.com/CS2103-AY1819S1-T16-3/main/pull/102[#73], +https://github.com/CS2103-AY1819S1-T16-3/main/pull/128[#128], +https://github.com/CS2103-AY1819S1-T16-3/main/pull/171[#171] +https://github.com/CS2103-AY1819S1-T16-3/main/pull/183[#183]) + +** What it does: Users of this application have a visually appealing interface. They are able to view +the status of their orders, available deliverymen and key insights for their data all in one screen. + +** Justification: The UI is minimalistic and was designed with the end-user in mind. There is only one screen that +the user uses at all times and all essential details are on that screen. The UI updates itself based on the +actions of the user e.g. the map/dashboard will dynamically update when there is a change. + +-- + +* *Minor enhancements*: + +** Added essential fields such as address and date to order model +(https://github.com/CS2103-AY1819S1-T16-3/main/pull/37[#37], +https://github.com/CS2103-AY1819S1-T16-3/main/pull/37[#99]). +** Came up with `/home` command to return to the dashboard panel (https://github.com/CS2103-AY1819S1-T16-3/main/pull/123[#123]) +** Various Bug Fixes (https://github.com/CS2103-AY1819S1-T16-3/main/pull/101[#101], +https://github.com/CS2103-AY1819S1-T16-3/main/pull/101[#125], +https://github.com/CS2103-AY1819S1-T16-3/main/pull/185[#185]) + +* *Code contributions*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=rrtheonlyone[RepoSense] + +* *Other contributions*: + +** Project management: +*** Initiated launch of new features (dashboard/improved find etc.) +*** Guided team members on existing practices +*** Helped with the issue tracking using labels and assignees on Github. + +** Documentation: +*** Updated User Guide and Developer Guide for the features I added + +** Community: +*** Review pull requests weekly (https://github.com/CS2103-AY1819S1-T16-3/main/pulls?utf8=%E2%9C%93&q=is%3Apr+reviewed-by%3Arrtheonlyone[see here]) +*** Reported bugs and gave suggestions for other members's project ( +Issue https://github.com/CS2103-AY1819S1-T09-2/main/issues/221[#213], +https://github.com/CS2103-AY1819S1-T09-2/main/issues/221[#216], +https://github.com/CS2103-AY1819S1-T09-2/main/issues/221[#221], +https://github.com/CS2103-AY1819S1-T09-2/main/issues/225[#225], +https://github.com/CS2103-AY1819S1-T09-2/main/issues/226[#226], +https://github.com/CS2103-AY1819S1-T09-2/main/issues/230[#230], +https://github.com/CS2103-AY1819S1-T09-2/main/issues/236[#236] +) + +== Contributions to User Guide +|=== +|Given below are some of my contributions to the User Guide. They highlight my ability to write clear + documentation targeting end-users. +|=== + +include::../UserGuide.adoc[tag=home] + +include::../UserGuide.adoc[tag=orderAdd] + +== Contributions to Developer Guide +|=== +|Given below are some of my contributions 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=dashboard] + +include::../DeveloperGuide.adoc[tag=dashboard2] + +--- + +|=== +|Given below are some of the design considerations I had while implementing the dashboard feature. +|=== + +include::../DeveloperGuide.adoc[tag=dashboard3] + diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/seedu/address/AppParameters.java index ab552c398f3d..56c76c7f73f4 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/seedu/address/AppParameters.java @@ -18,14 +18,6 @@ public class AppParameters { private Path configPath; - public Path getConfigPath() { - return configPath; - } - - public void setConfigPath(Path configPath) { - this.configPath = configPath; - } - /** * Parses the application command-line parameters. */ @@ -43,6 +35,14 @@ public static AppParameters parse(Application.Parameters parameters) { return appParameters; } + public Path getConfigPath() { + return configPath; + } + + public void setConfigPath(Path configPath) { + this.configPath = configPath; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index ecdd043a4f81..e0de017d0a9b 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -20,18 +20,23 @@ 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.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.OrderBook; +import seedu.address.model.ReadOnlyOrderBook; +import seedu.address.model.ReadOnlyUsersList; import seedu.address.model.UserPrefs; +import seedu.address.model.UsersList; +import seedu.address.model.deliveryman.DeliverymenList; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; +import seedu.address.storage.FoodZoomStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; -import seedu.address.storage.XmlAddressBookStorage; +import seedu.address.storage.XmlFoodZoomStorage; +import seedu.address.storage.user.UsersListStorage; +import seedu.address.storage.user.XmlUsersListStorage; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; @@ -51,10 +56,13 @@ public class MainApp extends Application { protected Config config; protected UserPrefs userPrefs; + public static void main(String[] args) { + launch(args); + } @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing OrderBook ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -62,8 +70,9 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new XmlAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + FoodZoomStorage foodZoomStorage = new XmlFoodZoomStorage(userPrefs.getFoodZoomFilePath()); + UsersListStorage usersListStorage = new XmlUsersListStorage(userPrefs.getUsersListFilePath()); + storage = new StorageManager(usersListStorage, foodZoomStorage, userPrefsStorage); initLogging(config); @@ -77,28 +86,61 @@ 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. + * Returns a {@code ModelManager} with the data from {@code storage}'s order book and {@code userPrefs}.
+ * The data from the sample order book will be used instead if {@code storage}'s order book is not found, + * or an empty order book will be used instead if errors occur when reading {@code storage}'s order book. */ private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional orderBookOptional; + ReadOnlyOrderBook initialData; + Optional usersListOptional; + ReadOnlyUsersList initialUser; + Optional deliverymenListOptional; + DeliverymenList initialDeliverymenData; + + try { + orderBookOptional = storage.readOrderBook(); + if (!orderBookOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample OrderBook"); + } + initialData = orderBookOptional.orElseGet(SampleDataUtil::getSampleOrderBook); + } catch (DataConversionException e) { + logger.warning("Data file not in the correct format. Will be starting with an empty OrderBook"); + initialData = new OrderBook(); + } catch (IOException e) { + logger.warning("Problem while reading from the file. Will be starting with an empty OrderBook"); + initialData = new OrderBook(); + } + try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + usersListOptional = storage.readUsersList(); + if (!usersListOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample UsersList"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialUser = usersListOptional.orElseGet(SampleDataUtil::getSampleUsersList); } 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 UsersList"); + initialUser = new UsersList(); } 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 UsersList"); + initialUser = new UsersList(); } - return new ModelManager(initialData, userPrefs); + try { + deliverymenListOptional = storage.readDeliverymenList(); + if (!deliverymenListOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample DeliverymenList"); + } + initialDeliverymenData = deliverymenListOptional.orElseGet(SampleDataUtil::getSampleDeliverymenList); + } catch (DataConversionException e) { + logger.warning("Data file not in the correct format. Will be starting with an empty DeliverymenList"); + initialDeliverymenData = new DeliverymenList(); + } catch (IOException e) { + logger.warning("Problem while reading from the file. Will be starting with an empty DeliverymenList"); + initialDeliverymenData = new DeliverymenList(); + } + + return new ModelManager(initialData, initialUser, initialDeliverymenData, userPrefs); } private void initLogging(Config config) { @@ -159,7 +201,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 OrderBook"); initializedPrefs = new UserPrefs(); } @@ -179,7 +221,7 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting OrderBook " + MainApp.VERSION); ui.start(primaryStage); } @@ -201,8 +243,4 @@ public void handleExitAppRequestEvent(ExitAppRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); stop(); } - - public static void main(String[] args) { - launch(args); - } } diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index e978d621e086..1c1a2d26ff64 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -13,7 +13,7 @@ public class Config { public static final Path DEFAULT_CONFIG_FILE = Paths.get("config.json"); // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "Food Zoom"; private Level logLevel = Level.INFO; private Path userPrefsFilePath = Paths.get("preferences.json"); diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 5316a1d87d3e..1270b34c0878 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -14,17 +14,17 @@ * Configures and manages loggers and handlers, including their logging level * Named {@link Logger}s can be obtained from this class
* These loggers have been configured to output messages to the console and a {@code .log} file by default, - * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log - * file reaches 5MB big, up to a maximum of 5 files.
+ * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log + * file reaches 5MB big, up to a maximum of 5 files.
*/ 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 Level currentLogLevel = Level.INFO; - private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; private static ConsoleHandler consoleHandler; + private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); /** * Initializes with a custom log level (specified in the {@code config} object) @@ -97,6 +97,7 @@ private static void addFileHandler(Logger logger) { /** * Creates a {@code FileHandler} for the log file. + * * @throws IOException if there are problems opening the file. */ private static FileHandler createFileHandler() throws IOException { diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..2c0f1b8b8c78 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -1,5 +1,7 @@ package seedu.address.commons.core; +import static seedu.address.model.deliveryman.Deliveryman.ORDERS_LIMIT; + /** * Container for user visible messages. */ @@ -7,7 +9,26 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; 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_INVALID_ROUTE_COMMAND_FORMAT = "Invalid route command format! \n%1$s"; + public static final String MESSAGE_INVALID_ORDER_COMMAND_FORMAT = "Invalid order command format! \n%1$s"; + public static final String MESSAGE_INVALID_ORDER_DISPLAYED_INDEX = "The order index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - + public static final String MESSAGE_ORDERS_LISTED_OVERVIEW = "%1$d orders listed!"; + public static final String MESSAGE_COMPLETED_ORDER = "This order is already delivered to the customer."; + public static final String MESSAGE_INVALID_DELIVERYMAN_DISPLAYED_INDEX = + "The deliveryman index provided is invalid"; + public static final String MESSAGE_INVALID_DELIVERYMAN_COMMAND_FORMAT = + "Invalid deliveryman command format! \n%1$s"; + public static final String MESSAGE_DELIVERYMEN_LISTED_OVERVIEW = "%1$d deliverymen listed!"; + public static final String MESSAGE_DELIVERYMEN_HAS_ORDERS_CANNOT_DELETE = "Unable to delete deliveryman %1$s, " + + "currently assigned to some orders."; + public static final String MESSAGE_ORDER_HAS_DELIVERYMAN_CANNOT_DELETE = "Unable to delete order, " + + "already assigned to a deliveryman."; + public static final String MESSAGE_ORDER_ALREADY_ASSIGNED_TO_DELIVERYMAN = "Order %1$s is already assigned to " + + "deliveryman %2$s, cannot be edited or reassigned to another deliveryman!"; + public static final String MESSAGE_ORDER_ONGOING_CANNOT_CLEAR = "There is at least one ongoing order in the list, " + + "order list cannot be cleared."; + public static final String MESSAGE_REQUIRE_LOGIN = "Please login first!"; + public static final String MESSAGE_ORDERS_LIMIT_EXCEEDED = String.format( + "You cannot assign more than %1$d orders to a deliveryman.", ORDERS_LIMIT); } diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/address/commons/core/Version.java index e117f91b3b2e..f6acef824e79 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/address/commons/core/Version.java @@ -29,24 +29,9 @@ public Version(int major, int minor, int patch, boolean isEarlyAccess) { this.isEarlyAccess = isEarlyAccess; } - public int getMajor() { - return major; - } - - public int getMinor() { - return minor; - } - - public int getPatch() { - return patch; - } - - public boolean isEarlyAccess() { - return isEarlyAccess; - } - /** * Parses a version number string in the format V1.2.3. + * * @param versionString version number string * @return a Version object */ @@ -64,6 +49,22 @@ public static Version fromString(String versionString) throws IllegalArgumentExc versionMatcher.group(4) == null ? false : true); } + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getPatch() { + return patch; + } + + public boolean isEarlyAccess() { + return isEarlyAccess; + } + @JsonValue public String toString() { return String.format("V%d.%d.%d%s", major, minor, patch, isEarlyAccess ? "ea" : ""); 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 19536439c099..adacc0a753fe 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -1,5 +1,7 @@ package seedu.address.commons.core.index; +import java.util.Objects; + /** * Represents a zero-based or one-based index. * @@ -23,14 +25,6 @@ private Index(int zeroBasedIndex) { this.zeroBasedIndex = zeroBasedIndex; } - public int getZeroBased() { - return zeroBasedIndex; - } - - public int getOneBased() { - return zeroBasedIndex + 1; - } - /** * Creates a new {@code Index} using a zero-based index. */ @@ -45,6 +39,20 @@ public static Index fromOneBased(int oneBasedIndex) { return new Index(oneBasedIndex - 1); } + public int getZeroBased() { + return zeroBasedIndex; + } + + public int getOneBased() { + return zeroBasedIndex + 1; + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(zeroBasedIndex); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java deleted file mode 100644 index b72ad4740e5a..000000000000 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.commons.events.model; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.ReadOnlyAddressBook; - -/** Indicates the AddressBook in the model has changed*/ -public class AddressBookChangedEvent extends BaseEvent { - - public final ReadOnlyAddressBook data; - - public AddressBookChangedEvent(ReadOnlyAddressBook data) { - this.data = data; - } - - @Override - public String toString() { - return "number of persons " + data.getPersonList().size(); - } -} diff --git a/src/main/java/seedu/address/commons/events/model/FoodZoomChangedEvent.java b/src/main/java/seedu/address/commons/events/model/FoodZoomChangedEvent.java new file mode 100644 index 000000000000..ea6346010218 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/FoodZoomChangedEvent.java @@ -0,0 +1,25 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.ReadOnlyOrderBook; +import seedu.address.model.deliveryman.DeliverymenList; + +/** + * Indicates that some part of the app has changed. + */ +public class FoodZoomChangedEvent extends BaseEvent { + + public final ReadOnlyOrderBook orderBook; + + public final DeliverymenList deliverymenList; + + public FoodZoomChangedEvent(ReadOnlyOrderBook orderBook, DeliverymenList deliverymenList) { + this.orderBook = orderBook; + this.deliverymenList = deliverymenList; + } + + @Override + public String toString() { + return "number of orders " + orderBook.getOrderList().size() + deliverymenList.getDeliverymenList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/model/UserLoggedInEvent.java b/src/main/java/seedu/address/commons/events/model/UserLoggedInEvent.java new file mode 100644 index 000000000000..66a8a5a061c8 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/UserLoggedInEvent.java @@ -0,0 +1,21 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.user.User; + +/** + * Indicates the user has logged in + */ +public class UserLoggedInEvent extends BaseEvent { + + public final User data; + + public UserLoggedInEvent(User data) { + this.data = data; + } + + @Override + public String toString() { + return "User " + data.getUsername() + " have logged in"; + } +} diff --git a/src/main/java/seedu/address/commons/events/model/UserLoggedOutEvent.java b/src/main/java/seedu/address/commons/events/model/UserLoggedOutEvent.java new file mode 100644 index 000000000000..b50e567e8e84 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/UserLoggedOutEvent.java @@ -0,0 +1,18 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates the user has logged out. + */ +public class UserLoggedOutEvent extends BaseEvent { + + + public UserLoggedOutEvent() { + } + + @Override + public String toString() { + return "User have successfully logout."; + } +} diff --git a/src/main/java/seedu/address/commons/events/model/UsersListChangedEvent.java b/src/main/java/seedu/address/commons/events/model/UsersListChangedEvent.java new file mode 100644 index 000000000000..20890a8ac62f --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/UsersListChangedEvent.java @@ -0,0 +1,19 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.ReadOnlyUsersList; + +/** Indicates the UsersList in the model has changed*/ +public class UsersListChangedEvent extends BaseEvent { + + public final ReadOnlyUsersList data; + + public UsersListChangedEvent(ReadOnlyUsersList data) { + this.data = data; + } + + @Override + public String toString() { + return "number of users " + data.getUserList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/BackToHomeEvent.java b/src/main/java/seedu/address/commons/events/ui/BackToHomeEvent.java new file mode 100644 index 000000000000..c2cc89fb228d --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/BackToHomeEvent.java @@ -0,0 +1,14 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates a request to go back to the dashboard screen + */ +public class BackToHomeEvent extends BaseEvent { + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/DeliveryManPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/DeliveryManPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..7c559ef2e42a --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/DeliveryManPanelSelectionChangedEvent.java @@ -0,0 +1,26 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.deliveryman.Deliveryman; + +/** + * Represents a selection change in the Person List Panel + */ +public class DeliveryManPanelSelectionChangedEvent extends BaseEvent { + + + private final Deliveryman newSelection; + + public DeliveryManPanelSelectionChangedEvent(Deliveryman newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public Deliveryman getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToDeliveryManListRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/JumpToDeliveryManListRequestEvent.java new file mode 100644 index 000000000000..1242ca98634d --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/JumpToDeliveryManListRequestEvent.java @@ -0,0 +1,22 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates a request to jump to the list of order + */ +public class JumpToDeliveryManListRequestEvent extends BaseEvent { + + public final int targetIndex; + + public JumpToDeliveryManListRequestEvent(Index targetIndex) { + this.targetIndex = targetIndex.getZeroBased(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/JumpToOrderListRequestEvent.java similarity index 66% rename from src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java rename to src/main/java/seedu/address/commons/events/ui/JumpToOrderListRequestEvent.java index a890f8b47350..15a18a3a6e4d 100644 --- a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java +++ b/src/main/java/seedu/address/commons/events/ui/JumpToOrderListRequestEvent.java @@ -4,13 +4,13 @@ import seedu.address.commons.events.BaseEvent; /** - * Indicates a request to jump to the list of persons + * Indicates a request to jump to the list of order */ -public class JumpToListRequestEvent extends BaseEvent { +public class JumpToOrderListRequestEvent extends BaseEvent { public final int targetIndex; - public JumpToListRequestEvent(Index targetIndex) { + public JumpToOrderListRequestEvent(Index targetIndex) { this.targetIndex = targetIndex.getZeroBased(); } diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/OrderPanelSelectionChangedEvent.java similarity index 57% rename from src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java rename to src/main/java/seedu/address/commons/events/ui/OrderPanelSelectionChangedEvent.java index c5c8b9ce90ed..632aea7cec22 100644 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ b/src/main/java/seedu/address/commons/events/ui/OrderPanelSelectionChangedEvent.java @@ -1,17 +1,17 @@ package seedu.address.commons.events.ui; import seedu.address.commons.events.BaseEvent; -import seedu.address.model.person.Person; +import seedu.address.model.order.Order; /** * Represents a selection change in the Person List Panel */ -public class PersonPanelSelectionChangedEvent extends BaseEvent { +public class OrderPanelSelectionChangedEvent extends BaseEvent { - private final Person newSelection; + private final Order newSelection; - public PersonPanelSelectionChangedEvent(Person newSelection) { + public OrderPanelSelectionChangedEvent(Order newSelection) { this.newSelection = newSelection; } @@ -20,7 +20,7 @@ public String toString() { return getClass().getSimpleName(); } - public Person getNewSelection() { + public Order getNewSelection() { return newSelection; } } diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java index 19124db485c9..651ce2902085 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java @@ -13,7 +13,7 @@ public IllegalValueException(String message) { /** * @param message should contain relevant information on the failed constraint(s) - * @param cause of the main exception + * @param cause of the main exception */ public IllegalValueException(String message, Throwable cause) { super(message, cause); diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/address/commons/util/CollectionUtil.java index eafe4dfd6818..942edda97d71 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/address/commons/util/CollectionUtil.java @@ -12,7 +12,9 @@ */ public class CollectionUtil { - /** @see #requireAllNonNull(Collection) */ + /** + * @see #requireAllNonNull(Collection) + */ public static void requireAllNonNull(Object... items) { requireNonNull(items); Stream.of(items).forEach(Objects::requireNonNull); diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java index b1e2767cdd92..f811215474cd 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/address/commons/util/FileUtil.java @@ -20,6 +20,7 @@ public static boolean isFileExists(Path file) { /** * Returns true if {@code path} can be converted into a {@code Path} via {@link Paths#get(String)}, * otherwise returns false. + * * @param path A string representing the file path. Cannot be null. */ public static boolean isValidPath(String path) { @@ -33,6 +34,7 @@ public static boolean isValidPath(String path) { /** * Creates a file if it does not exist along with its missing parent directories. + * * @throws IOException if the file or directory cannot be created. */ public static void createIfMissing(Path file) throws IOException { diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/address/commons/util/JsonUtil.java index 8ef609f055df..c589cf159fae 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/address/commons/util/JsonUtil.java @@ -51,7 +51,8 @@ static T deserializeObjectFromJsonFile(Path jsonFile, Class classOfObject /** * Returns the Json object from the given file or {@code Optional.empty()} object if the file is not found. * If any values are missing from the file, default values will be used, as long as the file is a valid json file. - * @param filePath cannot be null. + * + * @param filePath cannot be null. * @param classOfObjectToDeserialize Json file has to correspond to the structure in the class given here. * @throws DataConversionException if the file format is not as expected. */ @@ -79,6 +80,7 @@ public static Optional readJsonFile( /** * Saves the Json object to the specified file. * Overwrites existing file if it exists, creates a new file if it doesn't. + * * @param jsonFile cannot be null * @param filePath cannot be null * @throws IOException if there was an error during writing to the file @@ -93,6 +95,7 @@ public static void saveJsonFile(T jsonFile, Path filePath) throws IOExceptio /** * Converts a given string representation of a JSON data to instance of a class + * * @param The generic type to create an instance of * @return The instance of T with the specified values in the JSON string */ @@ -102,8 +105,9 @@ public static T fromJsonString(String json, Class instanceClass) throws I /** * Converts a given instance of a class into its JSON data string representation + * * @param instance The T object to be converted into the JSON string - * @param The generic type to create an instance of + * @param The generic type to create an instance of * @return JSON data representation of the given class instance, in string */ public static String toJsonString(T instance) throws JsonProcessingException { @@ -126,9 +130,8 @@ protected Level _deserialize(String value, DeserializationContext ctxt) { /** * Gets the logging level that matches loggingLevelString - *

- * Returns null if there are no matches * + * Returns null if there are no matches */ private Level getLoggingLevel(String loggingLevelString) { return Level.parse(loggingLevelString); diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb8..8f42853e4cc0 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -14,28 +14,29 @@ public class StringUtil { /** * Returns true if the {@code sentence} contains the {@code word}. - * Ignores case, but a full word match is required. - *
examples:

+     * Ignores case, but a full word match is required.
+     * 
examples:
      *       containsWordIgnoreCase("ABc def", "abc") == true
      *       containsWordIgnoreCase("ABc def", "DEF") == true
      *       containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
      *       
+ * * @param sentence cannot be null - * @param word cannot be null, cannot be empty, must be a single word + * @param word cannot be null, cannot be empty, must be a single word */ public static boolean containsWordIgnoreCase(String sentence, String word) { requireNonNull(sentence); requireNonNull(word); - String preppedWord = word.trim(); + String preppedWord = word.trim().toLowerCase(); checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); - String preppedSentence = sentence; + String preppedSentence = sentence.toLowerCase(); String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); return Arrays.stream(wordsInPreppedSentence) - .anyMatch(preppedWord::equalsIgnoreCase); + .anyMatch(wordInPreppedSentence -> wordInPreppedSentence.contains(preppedWord)); } /** @@ -53,6 +54,7 @@ public static String getDetails(Throwable t) { * e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE}
* Will return false for any other non-null string input * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * * @throws NullPointerException if {@code s} is null. */ public static boolean isNonZeroUnsignedInteger(String s) { diff --git a/src/main/java/seedu/address/logic/ListElementPointer.java b/src/main/java/seedu/address/logic/ListElementPointer.java index ca4085d98a11..ea0d86ccab2e 100644 --- a/src/main/java/seedu/address/logic/ListElementPointer.java +++ b/src/main/java/seedu/address/logic/ListElementPointer.java @@ -61,6 +61,7 @@ private boolean isWithinBounds(int index) { /** * Returns the next element in the list and advances the cursor position. + * * @throws NoSuchElementException if there is no more next element in the list. */ public String next() { @@ -72,6 +73,7 @@ public String next() { /** * Returns the previous element in the list and moves the cursor position backwards. + * * @throws NoSuchElementException if there is no more previous element in the list. */ public String previous() { @@ -83,6 +85,7 @@ public String previous() { /** * Returns the current element in the list. + * * @throws NoSuchElementException if the list is empty. */ public String current() { diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..76d705b76a8a 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,7 +4,8 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Person; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.order.Order; /** * API of the Logic component @@ -12,16 +13,26 @@ public interface Logic { /** * Executes the command and returns the result. + * * @param commandText The command as entered by the user. * @return the result of the command execution. * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. + * @throws ParseException If an error occurs during parsing. */ CommandResult execute(String commandText) throws CommandException, ParseException; - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + /** + * Returns an unmodifiable view of the filtered list of order + */ + ObservableList getFilteredOrderList(); - /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ + /** + * Returns an unmodifiable view of the filtered list of delivery men + */ + ObservableList getFilteredDeliverymanList(); + + /** + * Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object + */ ListElementPointer getHistorySnapshot(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9aff86fc33dc..48e3bc436c4b 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -1,5 +1,7 @@ package seedu.address.logic; +import static seedu.address.commons.core.Messages.MESSAGE_REQUIRE_LOGIN; + import java.util.logging.Logger; import javafx.collections.ObservableList; @@ -7,11 +9,18 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.commands.LogoutCommand; +import seedu.address.logic.commands.SignUpCommand; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.OrderBookParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.order.Order; /** * The main LogicManager of the app. @@ -21,32 +30,51 @@ public class LogicManager extends ComponentManager implements Logic { private final Model model; private final CommandHistory history; - private final AddressBookParser addressBookParser; + private final OrderBookParser orderBookParser; public LogicManager(Model model) { this.model = model; history = new CommandHistory(); - addressBookParser = new AddressBookParser(); + orderBookParser = new OrderBookParser(); } @Override public CommandResult execute(String commandText) throws CommandException, ParseException { logger.info("----------------[USER COMMAND][" + commandText + "]"); try { - Command command = addressBookParser.parseCommand(commandText); - return command.execute(model, history); + Command command = orderBookParser.parseCommand(commandText); + if (model.isUserLoggedIn() || isNotAuthenticatedCommand(command)) { + return command.execute(model, history); + } else { + throw new CommandException(MESSAGE_REQUIRE_LOGIN); + } } finally { history.add(commandText); } } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredOrderList() { + return model.getFilteredOrderList(); + } + + @Override + public ObservableList getFilteredDeliverymanList() { + return model.getFilteredDeliverymenList(); } @Override public ListElementPointer getHistorySnapshot() { return new ListElementPointer(history.getHistory()); } + + /** + * Returns true if it is not an authenticated command. + */ + private boolean isNotAuthenticatedCommand(Command command) { + return command instanceof LoginCommand || command instanceof SignUpCommand + || command instanceof HelpCommand || command instanceof HistoryCommand + || command instanceof LogoutCommand || command instanceof ExitCommand; + + } } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index d88e831ff1ce..000000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,69 +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 seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + 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"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @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)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AssignCommand.java b/src/main/java/seedu/address/logic/commands/AssignCommand.java new file mode 100644 index 000000000000..a1b88e2c9991 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AssignCommand.java @@ -0,0 +1,115 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DELIVERYMAN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.deliveryman.exceptions.OrdersLimitExceededException; +import seedu.address.model.order.Order; + +/** + * Assigns multiple orders to a deliveryman with two way association. + */ +public class AssignCommand extends Command { + + public static final String COMMAND_WORD = "/assign"; + + public static final String MESSAGE_SUCCESS = "Assigned order %1$s successfully to deliveryman %2$s"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Assign orders to deliveryman. " + + "Parameters: " + + PREFIX_DELIVERYMAN + "DELIVERYMAN_ID " + + PREFIX_ORDER + "ORDER_ID \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_DELIVERYMAN + "1 " + + PREFIX_ORDER + "1 " + PREFIX_ORDER + "3"; + + private final Index deliverymanId; + private final Set orderIds; + + /** + * Creates an AssignCommand to add the specified {@code user} + */ + public AssignCommand(Index deliverymanId, Set orderIds) { + requireAllNonNull(deliverymanId, orderIds); + this.deliverymanId = deliverymanId; + this.orderIds = orderIds; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownDeliverymanList = model.getFilteredDeliverymenList(); + List lastShownOrderList = model.getFilteredOrderList(); + Set ordersToAdd = new HashSet<>(); + + if (deliverymanId.getZeroBased() >= lastShownDeliverymanList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DELIVERYMAN_DISPLAYED_INDEX); + } + Deliveryman deliverymanToAssign = lastShownDeliverymanList.get(deliverymanId.getZeroBased()); + + for (Index i : orderIds) { + if (i.getZeroBased() >= lastShownOrderList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ORDER_DISPLAYED_INDEX); + } + Order order = lastShownOrderList.get(i.getZeroBased()); + if (order.isCompleted()) { + throw new CommandException(Messages.MESSAGE_COMPLETED_ORDER); + } + + if (order.isAlreadyAssignedDeliveryman()) { + throw new CommandException(String.format(Messages.MESSAGE_ORDER_ALREADY_ASSIGNED_TO_DELIVERYMAN, + i.getOneBased(), order.getDeliveryman())); + } + ordersToAdd.add(order); + } + + Deliveryman assignedDeliveryman = new Deliveryman(deliverymanToAssign); + if (!assignedDeliveryman.canAccommodate(ordersToAdd)) { + throw new CommandException(Messages.MESSAGE_ORDERS_LIMIT_EXCEEDED); + } + + for (Order order : ordersToAdd) { + Order updatedOrder = new Order(order); + try { + updatedOrder.setDeliveryman(assignedDeliveryman); + } catch (OrdersLimitExceededException e) { + throw new CommandException(Messages.MESSAGE_ORDERS_LIMIT_EXCEEDED); + } + model.updateOrder(order, updatedOrder); + } + + model.updateFilteredOrderList(Model.PREDICATE_SHOW_ALL_ORDERS); + model.commitOrderBook(); + + model.updateDeliveryman(deliverymanToAssign, assignedDeliveryman); + model.updateFilteredDeliverymenList(Model.PREDICATE_SHOW_ALL_DELIVERYMEN); + model.commitDeliverymenList(); + + StringJoiner validSj = new StringJoiner(", "); + for (Index i : orderIds) { + validSj.add(Integer.toString(i.getOneBased())); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, validSj.toString(), assignedDeliveryman)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AssignCommand // instanceof handles nulls + && deliverymanId.equals(((AssignCommand) other).deliverymanId) + && orderIds.equals(((AssignCommand) other).orderIds)); + } +} 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 1f85bcfe85a8..000000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.logic.CommandHistory; -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, CommandHistory history) { - requireNonNull(model); - model.resetData(new AddressBook()); - model.commitAddressBook(); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 34e99d786ec6..ae3abbafcc21 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -12,7 +12,7 @@ public abstract class Command { /** * Executes the command and returns the result message. * - * @param model {@code Model} which the command should operate on. + * @param model {@code Model} which the command should operate on. * @param history {@code CommandHistory} which the command should operate on. * @return feedback message of the operation result for display * @throws CommandException If an error occurs during command execution. 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 a20e9d49eac7..000000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,55 +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.CommandHistory; -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, CommandHistory history) 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); - model.commitAddressBook(); - 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 dc782d8e230f..000000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,228 +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.CommandHistory; -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, CommandHistory history) 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.updatePerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - model.commitAddressBook(); - 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 e848fa918964..459ba00e66bf 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -10,9 +10,9 @@ */ public class ExitCommand extends Command { - public static final String COMMAND_WORD = "exit"; + public static final String COMMAND_WORD = "/exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting FoodZoom as requested ..."; @Override public CommandResult execute(Model model, CommandHistory history) { 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 beb178e3a3f5..000000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.logic.CommandHistory; -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, CommandHistory history) { - 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 66305e95d8f2..2858d1cdf026 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -10,7 +10,7 @@ */ public class HelpCommand extends Command { - public static final String COMMAND_WORD = "help"; + public static final String COMMAND_WORD = "/help"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + "Example: " + COMMAND_WORD; diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java index f1541fb57f20..50e321055951 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/address/logic/commands/HistoryCommand.java @@ -13,7 +13,7 @@ */ public class HistoryCommand extends Command { - public static final String COMMAND_WORD = "history"; + public static final String COMMAND_WORD = "/history"; public static final String MESSAGE_SUCCESS = "Entered commands (from most recent to earliest):\n%1$s"; public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; diff --git a/src/main/java/seedu/address/logic/commands/HomeCommand.java b/src/main/java/seedu/address/logic/commands/HomeCommand.java new file mode 100644 index 000000000000..a4f139af3a96 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/HomeCommand.java @@ -0,0 +1,27 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.BackToHomeEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; + +/** + * Logout the user from FoodZoom. + */ +public class HomeCommand extends Command { + + public static final String COMMAND_WORD = "/home"; + + public static final String MESSAGE_SUCCESS = "Back to Home Page! View your dashboard below."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Returns back to home screen with dashboard. "; + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + EventsCenter.getInstance().post(new BackToHomeEvent()); + return new CommandResult(MESSAGE_SUCCESS); + } +} 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 6d44824c7d1b..000000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,25 +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.logic.CommandHistory; -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, CommandHistory history) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/LoginCommand.java b/src/main/java/seedu/address/logic/commands/LoginCommand.java new file mode 100644 index 000000000000..ad13b0c196d9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LoginCommand.java @@ -0,0 +1,71 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.user.User; + +/** + * Login the user into FoodZoom. + */ +public class LoginCommand extends Command { + + public static final String COMMAND_WORD = "/login"; + + public static final String MESSAGE_SUCCESS = "Login Success for %1$s"; + + public static final String MESSAGE_FAILURE = "Login Failure for %1$s"; + + public static final String MESSAGE_ALREADY_LOGGED_IN = "You are already logged in as %1$s"; + + public static final String MESSAGE_REDIRECT_TO_LOGOUT = "Please logout before attempting to login/signup to " + + "another account"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Login to FoodZoom. " + + "Parameters: " + + PREFIX_USERNAME + "USERNAME " + + PREFIX_PASSWORD + "PASSWORD \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_USERNAME + "johndoe " + + PREFIX_PASSWORD + "johndoepassword"; + + private final User toLogin; + + /** + * Creates an LoginCommand to add the specified {@code user} + */ + public LoginCommand(User user) { + requireNonNull(user); + toLogin = user; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + if (model.isUserLoggedIn()) { + User loggedInUser = model.getLoggedInUserDetails(); + String result = String.format(MESSAGE_ALREADY_LOGGED_IN, loggedInUser.getUsername()) + + "\n" + + MESSAGE_REDIRECT_TO_LOGOUT; + throw new CommandException(result); + } + + if (model.isRegisteredUser(toLogin)) { + model.storeUserInSession(toLogin); + return new CommandResult(String.format(MESSAGE_SUCCESS, toLogin)); + } else { + return new CommandResult(String.format(MESSAGE_FAILURE, toLogin)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LoginCommand // instanceof handles nulls + && toLogin.equals(((LoginCommand) other).toLogin)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/LogoutCommand.java b/src/main/java/seedu/address/logic/commands/LogoutCommand.java new file mode 100644 index 000000000000..18c2eb00a93c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LogoutCommand.java @@ -0,0 +1,32 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Logout the user from FoodZoom. + */ +public class LogoutCommand extends Command { + + public static final String COMMAND_WORD = "/logout"; + + public static final String MESSAGE_SUCCESS = "Logout Success"; + + public static final String MESSAGE_FAILURE = "No User to logout"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Logout from FoodZoom. "; + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + if (model.isUserLoggedIn()) { + model.clearUserInSession(); + return new CommandResult(MESSAGE_SUCCESS); + } else { + return new CommandResult(MESSAGE_FAILURE); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java deleted file mode 100644 index 227771a4eef6..000000000000 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ /dev/null @@ -1,31 +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.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; - -/** - * Reverts the {@code model}'s address book to its previously undone state. - */ -public class RedoCommand extends Command { - - public static final String COMMAND_WORD = "redo"; - public static final String MESSAGE_SUCCESS = "Redo success!"; - public static final String MESSAGE_FAILURE = "No more commands to redo!"; - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (!model.canRedoAddressBook()) { - throw new CommandException(MESSAGE_FAILURE); - } - - model.redoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/SignUpCommand.java b/src/main/java/seedu/address/logic/commands/SignUpCommand.java new file mode 100644 index 000000000000..6db7043c65ce --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SignUpCommand.java @@ -0,0 +1,76 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.LoginCommand.MESSAGE_ALREADY_LOGGED_IN; +import static seedu.address.logic.commands.LoginCommand.MESSAGE_REDIRECT_TO_LOGOUT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.user.User; + +/** + * Sign up the user into FoodZoom. + */ +public class SignUpCommand extends Command { + + public static final String COMMAND_WORD = "/signup"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a user to FoodZoom. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_USERNAME + "USERNAME " + + PREFIX_PASSWORD + "PASSWORD \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_USERNAME + "johndoe " + + PREFIX_PASSWORD + "johndoepassword"; + + public static final String MESSAGE_SUCCESS = "New user added: %1$s"; + public static final String MESSAGE_LOGGED_IN = "You have been logged into FoodZoom."; + public static final String MESSAGE_DUPLICATE_USER = "This user already exists in FoodZoom."; + + private final User toAdd; + + /** + * Creates an SignUpCommand to add the specified {@code user} + */ + public SignUpCommand(User user) { + requireNonNull(user); + toAdd = user; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + if (model.isUserLoggedIn()) { + User loggedInUser = model.getLoggedInUserDetails(); + String result = String.format(MESSAGE_ALREADY_LOGGED_IN, loggedInUser.getUsername()) + + "\n" + + MESSAGE_REDIRECT_TO_LOGOUT; + throw new CommandException(result); + } + + if (model.hasUser(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_USER); + } + + model.addUser(toAdd); + model.storeUserInSession(toAdd); + model.commitUsersList(); + String result = String.format(MESSAGE_SUCCESS, toAdd) + + "\n" + + MESSAGE_LOGGED_IN; + return new CommandResult(result); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SignUpCommand // instanceof handles nulls + && toAdd.equals(((SignUpCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java deleted file mode 100644 index 40441264f346..000000000000 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ /dev/null @@ -1,31 +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.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; - -/** - * Reverts the {@code model}'s address book to its previous state. - */ -public class UndoCommand extends Command { - - public static final String COMMAND_WORD = "undo"; - public static final String MESSAGE_SUCCESS = "Undo success!"; - public static final String MESSAGE_FAILURE = "No more commands to undo!"; - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (!model.canUndoAddressBook()) { - throw new CommandException(MESSAGE_FAILURE); - } - - model.undoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanAddCommand.java b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanAddCommand.java new file mode 100644 index 000000000000..5dd350192f53 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanAddCommand.java @@ -0,0 +1,58 @@ +package seedu.address.logic.commands.deliveryman; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.deliveryman.Deliveryman; + +/** + * Represents the command to add a deliveryman. + */ +public class DeliverymanAddCommand extends DeliverymanCommand { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = DeliverymanCommand.COMMAND_WORD + " " + COMMAND_WORD + + ": Adds deliveryman to the list. \n" + + "Parameters: " + + PREFIX_NAME + "NAME \n" + + "Example: " + DeliverymanCommand.COMMAND_WORD + " " + COMMAND_WORD + " " + + PREFIX_NAME + "Lorem Ipsum"; + + public static final String MESSAGE_SUCCESS = "New deliveryman added: %1$s"; + public static final String MESSAGE_DUPLICATE_DELIVERYMAN = "This deliveryman already exists in the list."; + + private final Deliveryman toAdd; + + /** + * Creates an DeliverymanAddCommand to add the specified {@code Deliverman} + */ + public DeliverymanAddCommand(Deliveryman dman) { + requireNonNull(dman); + toAdd = dman; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.hasDeliveryman(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_DELIVERYMAN); + } + + model.addDeliveryman(toAdd); + model.commitDeliverymenList(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof DeliverymanAddCommand + && toAdd.isSameDeliveryman(((DeliverymanAddCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanCommand.java b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanCommand.java new file mode 100644 index 000000000000..8eb467edc024 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanCommand.java @@ -0,0 +1,10 @@ +package seedu.address.logic.commands.deliveryman; + +import seedu.address.logic.commands.Command; + +/** + * Represents the abstract class which all Deliveryman Commands extend from + */ +public abstract class DeliverymanCommand extends Command { + public static final String COMMAND_WORD = "/deliveryman"; +} diff --git a/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanDeleteCommand.java b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanDeleteCommand.java new file mode 100644 index 000000000000..de9b14a14fbd --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanDeleteCommand.java @@ -0,0 +1,62 @@ +package seedu.address.logic.commands.deliveryman; + +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.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.deliveryman.Deliveryman; + +/** + * Deletes a deliveryman identified using it's displayed index from the address book. + */ +public class DeliverymanDeleteCommand extends DeliverymanCommand { + + public static final String COMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = DeliverymanCommand.COMMAND_WORD + " " + COMMAND_WORD + + ": Deletes the deliveryman identified by the index number used in the displayed deliveryman list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + DeliverymanCommand.COMMAND_WORD + " " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_DELIVERYMAN_SUCCESS = "Deleted Deliveryman: %1$s"; + + private final Index targetIndex; + + public DeliverymanDeleteCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredDeliverymenList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DELIVERYMAN_DISPLAYED_INDEX); + } + + Deliveryman deliverymanToDelete = lastShownList.get(targetIndex.getZeroBased()); + + if (deliverymanToDelete.hasOrders()) { + throw new CommandException(String.format(Messages.MESSAGE_DELIVERYMEN_HAS_ORDERS_CANNOT_DELETE, + deliverymanToDelete)); + } + model.deleteDeliveryman(deliverymanToDelete); + model.commitDeliverymenList(); + return new CommandResult(String.format(MESSAGE_DELETE_DELIVERYMAN_SUCCESS, deliverymanToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeliverymanDeleteCommand // instanceof handles nulls + && targetIndex.equals(((DeliverymanDeleteCommand) other).targetIndex)); // state check + } +} + diff --git a/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanFindCommand.java b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanFindCommand.java new file mode 100644 index 000000000000..2e345e183c4a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanFindCommand.java @@ -0,0 +1,46 @@ +package seedu.address.logic.commands.deliveryman; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.deliveryman.DeliverymanNameContainsKeywordsPredicate; + +/** + * Finds and lists all deliveryman in deliveryman list whose name contains in the argument keywords. + * Keyword matching is case insensitive. + */ +public class DeliverymanFindCommand extends DeliverymanCommand { + public static final String COMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = DeliverymanCommand.COMMAND_WORD + " " + COMMAND_WORD + + ": Finds the deliveryman whose name contains any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: n/KEYWORD \n" + + "Example: " + DeliverymanCommand.COMMAND_WORD + " " + COMMAND_WORD + " n/Alex"; + + private final DeliverymanNameContainsKeywordsPredicate predicate; + + public DeliverymanFindCommand(DeliverymanNameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + model.updateFilteredDeliverymenList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_DELIVERYMEN_LISTED_OVERVIEW, model.getFilteredDeliverymenList().size()) + ); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeliverymanFindCommand // instanceof handles nulls + && predicate.equals(((DeliverymanFindCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanListCommand.java b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanListCommand.java new file mode 100644 index 000000000000..5f31061c61e2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanListCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.commands.deliveryman; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_DELIVERYMEN; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all deliverymen in the list to the user. + */ +public class DeliverymanListCommand extends DeliverymanCommand { + + public static final String COMMAND_WORD = "list"; + + public static final String MESSAGE_SUCCESS = "Listed all deliverymen"; + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + + model.updateFilteredDeliverymenList(PREDICATE_SHOW_ALL_DELIVERYMEN); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanSelectCommand.java b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanSelectCommand.java new file mode 100644 index 000000000000..111a4fbf8739 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deliveryman/DeliverymanSelectCommand.java @@ -0,0 +1,57 @@ +package seedu.address.logic.commands.deliveryman; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.ui.JumpToDeliveryManListRequestEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.deliveryman.Deliveryman; + +/** + * The Command that selects a deliveryman identified using its displayed index + */ +public class DeliverymanSelectCommand extends DeliverymanCommand { + public static final String COMMAND_WORD = "select"; + + public static final String MESSAGE_USAGE = DeliverymanCommand.COMMAND_WORD + " " + COMMAND_WORD + + ": Selects the deliveryman identified by the index number used in the displayed deliveryman list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + DeliverymanCommand.COMMAND_WORD + " " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SELECT_ORDER_SUCCESS = "Selected Deliveryman: %1$s"; + + private final Index targetIndex; + + public DeliverymanSelectCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + List filteredDeliverymanList = model.getFilteredDeliverymenList(); + + if (targetIndex.getZeroBased() >= filteredDeliverymanList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DELIVERYMAN_DISPLAYED_INDEX); + } + + EventsCenter.getInstance().post(new JumpToDeliveryManListRequestEvent(targetIndex)); + return new CommandResult(String.format(MESSAGE_SELECT_ORDER_SUCCESS, targetIndex.getOneBased())); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeliverymanSelectCommand // instanceof handles nulls + && targetIndex.equals(((DeliverymanSelectCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/order/AddCommand.java b/src/main/java/seedu/address/logic/commands/order/AddCommand.java new file mode 100644 index 000000000000..bacb4163bc8d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/order/AddCommand.java @@ -0,0 +1,72 @@ +package seedu.address.logic.commands.order; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FOOD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.order.Order; + +/** + * Adds an order to the order book. + */ +public class AddCommand extends OrderCommand { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + + ": Adds an order to the order book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_DATE + "DATETIME " + + PREFIX_FOOD + "FOOD...\n" + + "Example: " + OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25, 612344 " + + PREFIX_FOOD + "Roti Prata " + + PREFIX_FOOD + "Ice Milo " + + PREFIX_DATE + "12-10-2018 00:00:00"; + + public static final String MESSAGE_SUCCESS = "New order added: %1$s"; + public static final String MESSAGE_DUPLICATE_ORDER = "This order already exists in the order book"; + + private final Order toAdd; + + /** + * Creates an AddCommand to add the specified {@code Order} + */ + public AddCommand(Order order) { + requireNonNull(order); + toAdd = order; + + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.hasOrder(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_ORDER); + } + + model.addOrder(toAdd); + model.commitOrderBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddCommand // instanceof handles nulls + && toAdd.isSameOrder(((AddCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/order/ClearCommand.java b/src/main/java/seedu/address/logic/commands/order/ClearCommand.java new file mode 100644 index 000000000000..8ef01e8d2ae7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/order/ClearCommand.java @@ -0,0 +1,34 @@ +package seedu.address.logic.commands.order; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.OrderBook; +import seedu.address.model.order.Order; + +/** + * Clears the address book. + */ +public class ClearCommand extends OrderCommand { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "Order book has been cleared!"; + + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + for (Order order : model.getOrderBook().getOrderList()) { + if (order.isOngoing()) { + throw new CommandException(Messages.MESSAGE_ORDER_ONGOING_CANNOT_CLEAR); + } + } + model.resetData(new OrderBook()); + model.commitOrderBook(); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/order/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/order/DeleteCommand.java new file mode 100644 index 000000000000..fbc88a18f39d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/order/DeleteCommand.java @@ -0,0 +1,60 @@ +package seedu.address.logic.commands.order; + +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.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.order.Order; + +/** + * Deletes an order identified using it's displayed index from the order book. + */ +public class DeleteCommand extends OrderCommand { + + public static final String COMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + + ": Deletes the order identified by the index number used in the displayed order list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_ORDER_SUCCESS = "Deleted Order: %1$s"; + + private final Index targetIndex; + + public DeleteCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredOrderList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ORDER_DISPLAYED_INDEX); + } + + Order orderToDelete = lastShownList.get(targetIndex.getZeroBased()); + if (orderToDelete.isAlreadyAssignedDeliveryman() && orderToDelete.isOngoing()) { + throw new CommandException(Messages.MESSAGE_ORDER_HAS_DELIVERYMAN_CANNOT_DELETE); + } + + model.deleteOrder(orderToDelete); + model.commitOrderBook(); + return new CommandResult(String.format(MESSAGE_DELETE_ORDER_SUCCESS, orderToDelete)); + } + + @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/order/DoneCommand.java b/src/main/java/seedu/address/logic/commands/order/DoneCommand.java new file mode 100644 index 000000000000..d839de24440c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/order/DoneCommand.java @@ -0,0 +1,99 @@ +package seedu.address.logic.commands.order; + +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.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderStatus; + +/** + * Mark an order as COMPLETED. + */ +public class DoneCommand extends OrderCommand { + + public static final String COMMAND_WORD = "done"; + + public static final String MESSAGE_USAGE = OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + + ": Marks an order as COMPLETED \n" + + "Parameters: INDEX (must be a positive integer) \n" + + "Example: " + OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + " 1 "; + + + public static final String MESSAGE_COMPLETED_ORDER_SUCCESS = "Order %1$s have been completed."; + public static final String MESSAGE_ONGOING_ORDER = "Only ONGOING status can be marked as completed."; + public static final String MESSAGE_DELIVERYMAN_NOT_EXIST = "Deliveryman does not exist inside deliveryman list."; + + private final Index targetIndex; + + public DoneCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredOrderList(); + List lastShowDeliverymanList = model.getFilteredDeliverymenList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ORDER_DISPLAYED_INDEX); + } + + Order orderToBeCompleted = lastShownList.get(targetIndex.getZeroBased()); + + if (!orderToBeCompleted.getOrderStatus().equals(new OrderStatus("ONGOING"))) { + throw new CommandException(MESSAGE_ONGOING_ORDER); + } + + + + //fetch deliveryman from index because order's deliveryman not reliable. + Deliveryman deliverymanToRemoveOrder = orderToBeCompleted.getDeliveryman(); + Deliveryman correctDeliveryman = lastShowDeliverymanList.stream() + .filter(deliveryman -> deliveryman.equals(deliverymanToRemoveOrder)) + .findFirst() + .orElseThrow(() -> new CommandException(MESSAGE_DELIVERYMAN_NOT_EXIST)); + Deliveryman updatedDeliveryman = removeOrderFromDeliveryman(correctDeliveryman, orderToBeCompleted); + + orderToBeCompleted.setStatusCompleted(); + + model.updateOrder(orderToBeCompleted, orderToBeCompleted); + model.updateFilteredOrderList(Model.PREDICATE_SHOW_ALL_ORDERS); + model.commitOrderBook(); + + model.updateDeliveryman(correctDeliveryman, updatedDeliveryman); + model.updateFilteredDeliverymenList(Model.PREDICATE_SHOW_ALL_DELIVERYMEN); + model.commitDeliverymenList(); + + return new CommandResult(String.format(MESSAGE_COMPLETED_ORDER_SUCCESS, orderToBeCompleted)); + } + + /** + * Remove order from deliveryman. + */ + private static Deliveryman removeOrderFromDeliveryman(Deliveryman targetDeliveryman, Order targetOrder) { + assert targetDeliveryman != null; + assert targetOrder != null; + + Deliveryman removedOrderDeliveryman = new Deliveryman(targetDeliveryman); + removedOrderDeliveryman.removeOrder(targetOrder); + + return removedOrderDeliveryman; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DoneCommand // instanceof handles nulls + && targetIndex.equals(((DoneCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/order/EditCommand.java b/src/main/java/seedu/address/logic/commands/order/EditCommand.java new file mode 100644 index 000000000000..200af0e0e521 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/order/EditCommand.java @@ -0,0 +1,249 @@ +package seedu.address.logic.commands.order; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FOOD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ORDERS; + +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.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.common.Address; +import seedu.address.model.common.Name; +import seedu.address.model.common.Phone; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.order.Food; +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderDate; +import seedu.address.model.order.OrderStatus; + + +/** + * Edits an order to the order book + */ +public class EditCommand extends OrderCommand { + + public static final String COMMAND_WORD = "edit"; + + public static final String MESSAGE_USAGE = OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + + ": Edits the details of the order identified " + + "by the index number used in the displayed order book. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_DATE + "DATE] " + + "[" + PREFIX_FOOD + "FOOD]...\n" + + "Example: " + OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + " 1 " + + PREFIX_PHONE + "91234567 " + + PREFIX_FOOD + "Roti Prata " + + PREFIX_FOOD + "Ice Milo"; + + public static final String MESSAGE_EDIT_ORDER_SUCCESS = "Edited Order: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_ORDER = "This order already exists in the order book."; + public static final String MESSAGE_INVALID_ORDER_INDEX = "Invalid Order Index: %1$s"; + + private final Index index; + private final EditOrderDescriptor editOrderDescriptor; + + /** + * @param index of the order in the filtered order list to edit + * @param editOrderDescriptor details to edit the order with + */ + public EditCommand(Index index, EditOrderDescriptor editOrderDescriptor) { + requireNonNull(index); + requireNonNull(editOrderDescriptor); + + this.index = index; + this.editOrderDescriptor = new EditOrderDescriptor(editOrderDescriptor); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredOrderList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ORDER_DISPLAYED_INDEX); + } + + Order orderToEdit = lastShownList.get(index.getZeroBased()); + Order editedOrder = createEditedOrder(orderToEdit, editOrderDescriptor); + + + if (!orderToEdit.isSameOrder(editedOrder) && model.hasOrder(editedOrder)) { + throw new CommandException(MESSAGE_DUPLICATE_ORDER); + } + + if (editedOrder.isCompleted()) { + throw new CommandException(Messages.MESSAGE_COMPLETED_ORDER); + } + + if (editedOrder.isAlreadyAssignedDeliveryman()) { + throw new CommandException(String.format(Messages.MESSAGE_ORDER_ALREADY_ASSIGNED_TO_DELIVERYMAN, + index.getOneBased(), editedOrder.getDeliveryman())); + } + + model.updateOrder(orderToEdit, editedOrder); + model.updateFilteredOrderList(PREDICATE_SHOW_ALL_ORDERS); + model.commitOrderBook(); + return new CommandResult(String.format(MESSAGE_EDIT_ORDER_SUCCESS, editedOrder)); + + } + + /** + * Creates and returns a {@code Order} with the details of {@code orderToEdit} + * edited with {@code editOrderDescriptor}. + */ + private static Order createEditedOrder(Order orderToEdit, EditOrderDescriptor editOrderDescriptor) { + assert orderToEdit != null; + + Name updatedName = editOrderDescriptor.getName().orElse(orderToEdit.getName()); + Phone updatedPhone = editOrderDescriptor.getPhone().orElse(orderToEdit.getPhone()); + Address updatedAddress = editOrderDescriptor.getAddress().orElse(orderToEdit.getAddress()); + OrderDate updatedDate = editOrderDescriptor.getDate().orElse(orderToEdit.getDate()); + Set updatedFood = editOrderDescriptor.getFood().orElse(orderToEdit.getFood()); + OrderStatus orderStatus = orderToEdit.getOrderStatus(); + Deliveryman deliveryman = orderToEdit.getDeliveryman(); + + return new Order(updatedName, updatedPhone, updatedAddress, updatedDate, orderStatus, updatedFood, deliveryman); + } + + @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) + && editOrderDescriptor.equals(e.editOrderDescriptor); + } + + /** + * Stores the details to edit the order with. Each non-empty field value will replace the + * corresponding field value of the order. + */ + public static class EditOrderDescriptor { + private Name name; + private Phone phone; + private Address address; + private OrderDate orderDate; + private Set food; + + public EditOrderDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code food} is used internally. + */ + public EditOrderDescriptor(EditOrderDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setAddress(toCopy.address); + setDate(toCopy.orderDate); + setFood(toCopy.food); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, phone, address, orderDate, food); + } + + 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 setAddress(Address address) { + this.address = address; + } + + public Optional
getAddress() { + return Optional.ofNullable(address); + } + + public void setDate(OrderDate orderDate) { + this.orderDate = orderDate; + } + + public Optional getDate() { + return Optional.ofNullable(orderDate); + } + + /** + * Sets {@code food} to this object's {@code food}. + * A defensive copy of {@code food} is used internally. + */ + public void setFood(Set food) { + this.food = (food != null) ? new HashSet<>(food) : null; + } + + /** + * Returns an unmodifiable food set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getFood() { + return (food != null) ? Optional.of(Collections.unmodifiableSet(food)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditOrderDescriptor)) { + return false; + } + + // state check + EditOrderDescriptor e = (EditOrderDescriptor) other; + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getAddress().equals(e.getAddress()) + && getDate().equals(e.getDate()) + && getFood().equals(e.getFood()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/order/FindCommand.java b/src/main/java/seedu/address/logic/commands/order/FindCommand.java new file mode 100644 index 000000000000..ecf5b7c4eba3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/order/FindCommand.java @@ -0,0 +1,50 @@ +package seedu.address.logic.commands.order; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.order.Order; + + +/** + * Finds and lists all orders in orders list whose name or phone contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindCommand extends OrderCommand { + public static final String COMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + + ": Finds the order whose keywords contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: [n/NAME] [p/PHONE] [a/ADDRESS] [dt/DATETIME] [f/FOOD] [st/ORDER_STATUS]\n" + + "Example: " + OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + " n/John f/Ice Milo\n" + + "Example: " + OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + + " dt/01-10-2018 10:00:00 dt/03-10-2018 10:00:00"; + + private final Predicate predicate; + + public FindCommand(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + model.updateFilteredOrderList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_ORDERS_LISTED_OVERVIEW, model.getFilteredOrderList().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/order/ListCommand.java b/src/main/java/seedu/address/logic/commands/order/ListCommand.java new file mode 100644 index 000000000000..b8c7481d2174 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/order/ListCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.commands.order; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ORDERS; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all orders in the order book to the user. + */ +public class ListCommand extends OrderCommand { + + public static final String COMMAND_WORD = "list"; + + public static final String MESSAGE_SUCCESS = "Listed all orders"; + + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.updateFilteredOrderList(PREDICATE_SHOW_ALL_ORDERS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/order/OrderCommand.java b/src/main/java/seedu/address/logic/commands/order/OrderCommand.java new file mode 100644 index 000000000000..0e3be7fc8497 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/order/OrderCommand.java @@ -0,0 +1,10 @@ +package seedu.address.logic.commands.order; + +import seedu.address.logic.commands.Command; + +/** + * Base Command for all order commands + */ +public abstract class OrderCommand extends Command { + public static final String COMMAND_WORD = "/order"; +} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/order/SelectCommand.java similarity index 51% rename from src/main/java/seedu/address/logic/commands/SelectCommand.java rename to src/main/java/seedu/address/logic/commands/order/SelectCommand.java index f5e8c1a8722e..95bd6ba5245e 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/address/logic/commands/order/SelectCommand.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.order; import static java.util.Objects.requireNonNull; @@ -7,25 +7,26 @@ import seedu.address.commons.core.EventsCenter; import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; -import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.commons.events.ui.JumpToOrderListRequestEvent; import seedu.address.logic.CommandHistory; +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.order.Order; /** - * Selects a person identified using it's displayed index from the address book. + * Selects an order identified using it's displayed index from the order book. */ -public class SelectCommand extends Command { +public class SelectCommand extends OrderCommand { public static final String COMMAND_WORD = "select"; - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the displayed person list.\n" + public static final String MESSAGE_USAGE = OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + + ": Selects the order identified by the index number used in the displayed order list.\n" + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + "Example: " + OrderCommand.COMMAND_WORD + " " + COMMAND_WORD + " 1"; - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; + public static final String MESSAGE_SELECT_ORDER_SUCCESS = "Selected Order: %1$s"; private final Index targetIndex; @@ -37,14 +38,14 @@ public SelectCommand(Index targetIndex) { public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - List filteredPersonList = model.getFilteredPersonList(); + List filteredOrderList = model.getFilteredOrderList(); - if (targetIndex.getZeroBased() >= filteredPersonList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + if (targetIndex.getZeroBased() >= filteredOrderList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ORDER_DISPLAYED_INDEX); } - EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); + EventsCenter.getInstance().post(new JumpToOrderListRequestEvent(targetIndex)); + return new CommandResult(String.format(MESSAGE_SELECT_ORDER_SUCCESS, targetIndex.getOneBased())); } diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java index 954c8e18f8ea..1e32986959dc 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; /** * Stores mapping of prefixes to their respective arguments. @@ -15,7 +16,9 @@ */ public class ArgumentMultimap { - /** Prefixes mapped to their respective arguments**/ + /** + * Prefixes mapped to their respective arguments + **/ private final Map> argMultimap = new HashMap<>(); /** @@ -57,4 +60,11 @@ public List getAllValues(Prefix prefix) { public String getPreamble() { return getValue(new Prefix("")).orElse(""); } + + /** + * Returns all keys prefixes + */ + public Set getAllPrefixes() { + return argMultimap.keySet(); + } } diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java index 5c9aebfa4888..1b2ccb81019e 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java @@ -7,11 +7,11 @@ /** * Tokenizes arguments string of the form: {@code preamble value value ...}
- * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
+ * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
* 1. An argument's value can be an empty string e.g. the value of {@code k/} in the above example.
* 2. Leading and trailing whitespaces of an argument value will be discarded.
* 3. An argument may be repeated and all its values will be accumulated e.g. the value of {@code t/} - * in the above example.
+ * in the above example.
*/ public class ArgumentTokenizer { @@ -21,7 +21,7 @@ public class ArgumentTokenizer { * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixes Prefixes to tokenize the arguments string with - * @return ArgumentMultimap object that maps prefixes to their arguments + * @return ArgumentMultimap object that maps prefixes to their arguments */ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { List positions = findAllPrefixPositions(argsString, prefixes); @@ -33,7 +33,7 @@ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixes Prefixes to find in the arguments string - * @return List of zero-based prefix positions in the given arguments string + * @return List of zero-based prefix positions in the given arguments string */ private static List findAllPrefixPositions(String argsString, Prefix... prefixes) { return Arrays.stream(prefixes) @@ -82,7 +82,7 @@ private static int findPrefixPosition(String argsString, String prefix, int from * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixPositions Zero-based positions of all prefixes in {@code argsString} - * @return ArgumentMultimap object that maps prefixes to their arguments + * @return ArgumentMultimap object that maps prefixes to their arguments */ private static ArgumentMultimap extractArguments(String argsString, List prefixPositions) { @@ -114,8 +114,8 @@ private static ArgumentMultimap extractArguments(String argsString, List { + @Override + public AssignCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DELIVERYMAN, PREFIX_ORDER); + + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_DELIVERYMAN, PREFIX_ORDER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AssignCommand.MESSAGE_USAGE)); + } + + Index deliverymanId = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_DELIVERYMAN).get()); + Set orderIds = ParserUtil.parseIndexes(argMultimap.getAllValues(PREFIX_ORDER)); + + return new AssignCommand(deliverymanId, orderIds); + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..0573a5cae1cf 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -11,5 +11,13 @@ public class CliSyntax { 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/"); + public static final Prefix PREFIX_FOOD = new Prefix("f/"); + public static final Prefix PREFIX_DATE = new Prefix("dt/"); + public static final Prefix PREFIX_STATUS = new Prefix("st/"); + public static final Prefix PREFIX_ORDER = new Prefix("o/"); + public static final Prefix PREFIX_DELIVERYMAN = new Prefix("d/"); + + public static final Prefix PREFIX_USERNAME = new Prefix("u/"); + public static final Prefix PREFIX_PASSWORD = new Prefix("pw/"); } 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 845644b7dea1..000000000000 --- 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/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index b186a967cb94..000000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -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.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns an FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand 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[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/LoginCommandParser.java b/src/main/java/seedu/address/logic/parser/LoginCommandParser.java new file mode 100644 index 000000000000..bd4da612eb2e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/LoginCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.common.Password; +import seedu.address.model.common.Username; +import seedu.address.model.user.User; + +/** + * Parses input arguments and creates a new LoginCommand object + */ +public class LoginCommandParser implements Parser { + @Override + public LoginCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_USERNAME, PREFIX_PASSWORD); + + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_USERNAME, PREFIX_PASSWORD) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, LoginCommand.MESSAGE_USAGE)); + } + + + Username username = ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME).get()); + Password password = ParserUtil.parsePassword(argMultimap.getValue(PREFIX_PASSWORD).get()); + + User user = new User(username, password); + + return new LoginCommand(user); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/OrderBookParser.java similarity index 58% rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java rename to src/main/java/seedu/address/logic/parser/OrderBookParser.java index b7d57f5db86a..5c6e5479e1b5 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/OrderBookParser.java @@ -6,25 +6,25 @@ 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.AssignCommand; 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.HistoryCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.commands.HomeCommand; +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.commands.LogoutCommand; +import seedu.address.logic.commands.SignUpCommand; +import seedu.address.logic.commands.deliveryman.DeliverymanCommand; +import seedu.address.logic.commands.order.OrderCommand; +import seedu.address.logic.parser.deliveryman.DeliverymanCommandParser; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.order.OrderCommandParser; /** * Parses user input. */ -public class AddressBookParser { +public class OrderBookParser { /** * Used for initial separation of command word and args. @@ -48,26 +48,11 @@ public Command parseCommand(String userInput) throws ParseException { final String arguments = matcher.group("arguments"); switch (commandWord) { - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); + case OrderCommand.COMMAND_WORD: + return new OrderCommandParser().parse(arguments); - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case SelectCommand.COMMAND_WORD: - return new SelectCommandParser().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 DeliverymanCommand.COMMAND_WORD: + return new DeliverymanCommandParser().parse(arguments); case HistoryCommand.COMMAND_WORD: return new HistoryCommand(); @@ -78,11 +63,20 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); - case UndoCommand.COMMAND_WORD: - return new UndoCommand(); + case SignUpCommand.COMMAND_WORD: + return new SignUpCommandParser().parse(arguments); + + case LoginCommand.COMMAND_WORD: + return new LoginCommandParser().parse(arguments); + + case LogoutCommand.COMMAND_WORD: + return new LogoutCommand(); + + case AssignCommand.COMMAND_WORD: + return new AssignCommandParser().parse(arguments); - case RedoCommand.COMMAND_WORD: - return new RedoCommand(); + case HomeCommand.COMMAND_WORD: + return new HomeCommand(); default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java index d6551ad8e3ff..ce644a9c6fdc 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/address/logic/parser/Parser.java @@ -10,6 +10,7 @@ public interface Parser { /** * Parses {@code userInput} into a command and returns it. + * * @throws ParseException if {@code userInput} does not conform the expected format */ T parse(String userInput) throws ParseException; diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 76daf40807e2..d8e7808df216 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -5,14 +5,18 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.stream.Stream; 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.common.Address; +import seedu.address.model.common.Name; +import seedu.address.model.common.Password; +import seedu.address.model.common.Phone; +import seedu.address.model.common.Username; +import seedu.address.model.order.Food; +import seedu.address.model.order.OrderDate; import seedu.address.model.tag.Tag; /** @@ -25,6 +29,7 @@ public class ParserUtil { /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { @@ -35,6 +40,19 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses {@code Collection tags} into a {@code Set}. + */ + public static Set parseIndexes(Collection index) throws ParseException { + requireNonNull(index); + final Set indexSet = new HashSet<>(); + for (String indexNo : index) { + indexSet.add(parseIndex(indexNo)); + } + return indexSet; + + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -81,18 +99,18 @@ public static Address parseAddress(String address) throws ParseException { } /** - * Parses a {@code String email} into an {@code Email}. + * Parses a {@code String orderDate} into a Java Date. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code email} is invalid. + * @throws ParseException if the given {@code orderDate} is invalid. */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_EMAIL_CONSTRAINTS); + public static OrderDate parseDate(String orderDate) throws ParseException { + requireNonNull(orderDate); + String trimmedDate = orderDate.trim(); + if (!OrderDate.isValidDate(trimmedDate)) { + throw new ParseException(OrderDate.MESSAGE_DATE_CONSTRAINTS); } - return new Email(trimmedEmail); + return new OrderDate(trimmedDate); } /** @@ -110,6 +128,21 @@ public static Tag parseTag(String tag) throws ParseException { return new Tag(trimmedTag); } + /** + * Parses a {@code String food} into a {@code Food}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code food} is invalid. + */ + public static Food parseFood(String food) throws ParseException { + requireNonNull(food); + String trimmedFood = food.trim(); + if (!Food.isValidFood(trimmedFood)) { + throw new ParseException(Food.MESSAGE_FOOD_CONSTRAINTS); + } + return new Food(trimmedFood); + } + /** * Parses {@code Collection tags} into a {@code Set}. */ @@ -121,4 +154,55 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses {@code Collection tags} into a {@code Set}. + */ + public static Set parseFoods(Collection food) throws ParseException { + requireNonNull(food); + final Set foodSet = new HashSet<>(); + for (String foodName : food) { + foodSet.add(parseFood(foodName)); + } + return foodSet; + + } + + /** + * Parses a {@code String username} into an {@code Username}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code username} is invalid. + */ + public static Username parseUsername(String username) throws ParseException { + requireNonNull(username); + String trimmedUsername = username.trim(); + if (!Username.isValidUsername(trimmedUsername)) { + throw new ParseException(Username.MESSAGE_USERNAME_CONSTRAINTS); + } + return new Username(trimmedUsername.toLowerCase()); + } + + /** + * Parses a {@code String password} into an {@code Password}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code password} is invalid. + */ + public static Password parsePassword(String password) throws ParseException { + requireNonNull(password); + String trimmedPassword = password.trim(); + if (!Password.isValidPassword(trimmedPassword)) { + throw new ParseException(Password.MESSAGE_PASSWORD_CONSTRAINTS); + } + return new Password(trimmedPassword); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + public 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/SignUpCommandParser.java b/src/main/java/seedu/address/logic/parser/SignUpCommandParser.java new file mode 100644 index 000000000000..16317a8e1523 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SignUpCommandParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import seedu.address.logic.commands.SignUpCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.common.Name; +import seedu.address.model.common.Password; +import seedu.address.model.common.Username; +import seedu.address.model.user.User; + +/** + * Parses input arguments and creates a new SignUpCommand object + */ +public class SignUpCommandParser implements Parser { + + @Override + public SignUpCommand parse(String args) throws ParseException { + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_USERNAME, PREFIX_PASSWORD); + + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_USERNAME, PREFIX_PASSWORD) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SignUpCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Username username = ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME).get()); + Password password = ParserUtil.parsePassword(argMultimap.getValue(PREFIX_PASSWORD).get()); + + User user = new User(name, username, password); + + return new SignUpCommand(user); + } +} diff --git a/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanAddCommandParser.java b/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanAddCommandParser.java new file mode 100644 index 000000000000..88ccad5938e1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanAddCommandParser.java @@ -0,0 +1,51 @@ +package seedu.address.logic.parser.deliveryman; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.deliveryman.DeliverymanAddCommand; +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.common.Name; +import seedu.address.model.deliveryman.Deliveryman; + +/** + * Parses input arguments and creates a new DeliverymanAddCommand object + */ +public class DeliverymanAddCommandParser implements Parser { + + /** + * Parses a user command into a DeliverymanAddCommand. + * + * @throws ParseException + */ + public DeliverymanAddCommand parse(String args) throws ParseException { + ArgumentMultimap argumentMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME); + + if (!arePrefixesPresent(argumentMultimap, PREFIX_NAME) || !argumentMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeliverymanAddCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(argumentMultimap.getValue(PREFIX_NAME).get()); + + Deliveryman deliveryman = new Deliveryman(name); + + return new DeliverymanAddCommand(deliveryman); + } + + /** + * 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/deliveryman/DeliverymanCommandParser.java b/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanCommandParser.java new file mode 100644 index 000000000000..4ffb30f488bc --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanCommandParser.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser.deliveryman; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DELIVERYMAN_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.HelpCommand; +import seedu.address.logic.commands.deliveryman.DeliverymanAddCommand; +import seedu.address.logic.commands.deliveryman.DeliverymanCommand; +import seedu.address.logic.commands.deliveryman.DeliverymanDeleteCommand; +import seedu.address.logic.commands.deliveryman.DeliverymanFindCommand; +import seedu.address.logic.commands.deliveryman.DeliverymanListCommand; +import seedu.address.logic.commands.deliveryman.DeliverymanSelectCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses deliveryman input. + */ +public class DeliverymanCommandParser { + + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + /** + * Parses the given {@code String} of arguments in the context of the DeliverymanCommand + * and returns an DeliverymanCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + + public DeliverymanCommand parse (String args) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_DELIVERYMAN_COMMAND_FORMAT, + HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + + switch (commandWord) { + + case DeliverymanAddCommand.COMMAND_WORD: + return new DeliverymanAddCommandParser().parse(arguments); + + case DeliverymanSelectCommand.COMMAND_WORD: + return new DeliverymanSelectCommandParser().parse(arguments); + + case DeliverymanDeleteCommand.COMMAND_WORD: + return new DeliverymanDeleteCommandParser().parse(arguments); + + case DeliverymanListCommand.COMMAND_WORD: + return new DeliverymanListCommand(); + + case DeliverymanFindCommand.COMMAND_WORD: + return new DeliverymanFindCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanDeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanDeleteCommandParser.java new file mode 100644 index 000000000000..20ece34b47da --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanDeleteCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser.deliveryman; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.deliveryman.DeliverymanDeleteCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses the input arguments to produce a DeliverymanDeleteCommand. + */ +public class DeliverymanDeleteCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments and returns a DeliverymanDeleteCommand + * object for execution. + * @throws ParseException if the input does not conform to the expected format + */ + public DeliverymanDeleteCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeliverymanDeleteCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeliverymanDeleteCommand.MESSAGE_USAGE), pe + ); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanFindCommandParser.java b/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanFindCommandParser.java new file mode 100644 index 000000000000..0d2c5d93520a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanFindCommandParser.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser.deliveryman; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.deliveryman.DeliverymanCommand; +import seedu.address.logic.commands.deliveryman.DeliverymanFindCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.deliveryman.DeliverymanNameContainsKeywordsPredicate; + +/** + * Parses the given {@code String} of arguments in the context of the DeliverymanFindCommand + * and returns an DeliverymanFindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ +public class DeliverymanFindCommandParser implements Parser { + public static final String MESSAGE_EMPTY_NAME_FIELD = "n/ cannot be empty"; + + /** + * Parses the given {@code String} of arguments in the context of the DeliverymanFindCommand + * and returns an DeliverymanFindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public DeliverymanCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeliverymanFindCommand.MESSAGE_USAGE)); + } + + String name = argMultimap.getValue(PREFIX_NAME).get().trim(); + + if (isEmptyField(name)) { + throw new ParseException(MESSAGE_EMPTY_NAME_FIELD); + } + + return new DeliverymanFindCommand(new DeliverymanNameContainsKeywordsPredicate(name)); + } + + /** + * 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()); + } + + private static boolean isEmptyField(String field) { + return field.equals(""); + } +} diff --git a/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanSelectCommandParser.java b/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanSelectCommandParser.java new file mode 100644 index 000000000000..77bfff638f14 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deliveryman/DeliverymanSelectCommandParser.java @@ -0,0 +1,28 @@ +package seedu.address.logic.parser.deliveryman; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.deliveryman.DeliverymanSelectCommand; +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 DeliverymanSelectCommand object. + */ +public class DeliverymanSelectCommandParser implements Parser { + /** + * Parses the fiven {@code string} of srgumants in the context of the DeliverymanSelectCommand + * and returns a DeliverymanSelectCommand object for execution. + */ + public DeliverymanSelectCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeliverymanSelectCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeliverymanSelectCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/order/AddCommandParser.java similarity index 61% rename from src/main/java/seedu/address/logic/parser/AddCommandParser.java rename to src/main/java/seedu/address/logic/parser/order/AddCommandParser.java index 3b8bfa035e83..f36fddaa9323 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/order/AddCommandParser.java @@ -1,60 +1,66 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.order; 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_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FOOD; 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.commands.order.AddCommand; +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.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.common.Address; +import seedu.address.model.common.Name; +import seedu.address.model.common.Phone; +import seedu.address.model.order.Food; +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderDate; /** * Parses input arguments and creates a new AddCommand object */ public class AddCommandParser implements Parser { + /** + * 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()); + } + /** * 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); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, PREFIX_DATE, PREFIX_FOOD); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_DATE, PREFIX_FOOD) || !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)); + OrderDate orderDate = ParserUtil.parseDate((argMultimap).getValue(PREFIX_DATE).get()); + Set foodList = ParserUtil.parseFoods(argMultimap.getAllValues(PREFIX_FOOD)); - Person person = new Person(name, phone, email, address, tagList); + Order order = new Order(name, phone, address, orderDate, foodList); - 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()); + return new AddCommand(order); } } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/order/DeleteCommandParser.java similarity index 83% rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java rename to src/main/java/seedu/address/logic/parser/order/DeleteCommandParser.java index 4d1f4bb0e4ec..e4fdaa9f5e95 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/order/DeleteCommandParser.java @@ -1,9 +1,11 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.order; 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.commands.order.DeleteCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -14,6 +16,7 @@ public class DeleteCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the DeleteCommand * and returns an DeleteCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { diff --git a/src/main/java/seedu/address/logic/parser/order/DoneCommandParser.java b/src/main/java/seedu/address/logic/parser/order/DoneCommandParser.java new file mode 100644 index 000000000000..245983d84735 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/order/DoneCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser.order; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.order.DoneCommand; +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 DoneCommand object + */ +public class DoneCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DoneCommand + * and returns an DoneCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public DoneCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DoneCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DoneCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/order/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/order/EditCommandParser.java new file mode 100644 index 000000000000..3d76e08c7447 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/order/EditCommandParser.java @@ -0,0 +1,88 @@ +package seedu.address.logic.parser.order; + +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_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FOOD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.Collection; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.order.EditCommand; +import seedu.address.logic.commands.order.EditCommand.EditOrderDescriptor; +import seedu.address.logic.commands.order.OrderCommand; +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.exceptions.ParseException; +import seedu.address.model.order.Food; + +/** + * 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_ADDRESS, PREFIX_DATE, PREFIX_FOOD); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); + } + + EditOrderDescriptor editOrderDescriptor = new EditOrderDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editOrderDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editOrderDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + editOrderDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + } + if (argMultimap.getValue(PREFIX_DATE).isPresent()) { + editOrderDescriptor.setDate(ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get())); + } + parseFoodForEdit(argMultimap.getAllValues(PREFIX_FOOD)).ifPresent(editOrderDescriptor::setFood); + + if (!editOrderDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } + + return new EditCommand(index, editOrderDescriptor); + } + + /** + * Parses {@code Collection food} into a {@code Set} if {@code food} is non-empty. + * If {@code food} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero food. + */ + private Optional> parseFoodForEdit(Collection food) throws ParseException { + assert food != null; + + if (food.isEmpty()) { + return Optional.empty(); + } + + if (food.size() == 1 && food.contains("")) { + throw new ParseException(String.format(Food.MESSAGE_FOOD_CONSTRAINTS)); + } + + return Optional.of(ParserUtil.parseFoods(food)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/order/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/order/FindCommandParser.java new file mode 100644 index 000000000000..5cb895f907f0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/order/FindCommandParser.java @@ -0,0 +1,59 @@ +package seedu.address.logic.parser.order; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_ORDER_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FOOD; +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_STATUS; + +import java.util.function.Predicate; +import java.util.stream.Stream; + +import seedu.address.logic.commands.order.FindCommand; +import seedu.address.logic.commands.order.OrderCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.Order; + +/** + * Parses the given {@code String} of arguments in the context of the OrderFindCommand + * and returns an OrderFindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ +public class FindCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the OrderFindCommand + * and returns an OrderFindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public OrderCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize( + args, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, PREFIX_DATE, PREFIX_FOOD, PREFIX_STATUS); + + if (!arePrefixesPresent( + argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, PREFIX_DATE, PREFIX_FOOD, PREFIX_STATUS) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_ORDER_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + Predicate suppliedPredicates = new OrderPredicateUtil().parsePredicate(argMultimap); + return new FindCommand(suppliedPredicates); + + } + + /** + * 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).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/order/OrderCommandParser.java b/src/main/java/seedu/address/logic/parser/order/OrderCommandParser.java new file mode 100644 index 000000000000..e38e670fbcb4 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/order/OrderCommandParser.java @@ -0,0 +1,73 @@ +package seedu.address.logic.parser.order; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_ORDER_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.HelpCommand; +import seedu.address.logic.commands.order.AddCommand; +import seedu.address.logic.commands.order.ClearCommand; +import seedu.address.logic.commands.order.DeleteCommand; +import seedu.address.logic.commands.order.DoneCommand; +import seedu.address.logic.commands.order.EditCommand; +import seedu.address.logic.commands.order.FindCommand; +import seedu.address.logic.commands.order.ListCommand; +import seedu.address.logic.commands.order.OrderCommand; +import seedu.address.logic.commands.order.SelectCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses order input. + */ +public class OrderCommandParser implements Parser { + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses the given {@code String} of arguments in the context of the OrderCommand + * and returns an OrderCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public OrderCommand parse(String args) throws ParseException { + + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_ORDER_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 SelectCommand.COMMAND_WORD: + return new SelectCommandParser().parse(arguments); + + case DeleteCommand.COMMAND_WORD: + return new DeleteCommandParser().parse(arguments); + + case ListCommand.COMMAND_WORD: + return new ListCommand(); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case FindCommand.COMMAND_WORD: + return new FindCommandParser().parse(arguments); + + case EditCommand.COMMAND_WORD: + return new EditCommandParser().parse(arguments); + + case DoneCommand.COMMAND_WORD: + return new DoneCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/order/OrderDatePredicateUtil.java b/src/main/java/seedu/address/logic/parser/order/OrderDatePredicateUtil.java new file mode 100644 index 000000000000..b2680f8ee144 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/order/OrderDatePredicateUtil.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser.order; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import seedu.address.logic.parser.exceptions.ParseException; + +import seedu.address.model.order.OrderDate; + +/** + * Parses of date keywords and returns a valid date list + */ +public class OrderDatePredicateUtil { + private static final int VALID_DATE_LIST_SIZE = 2; + + private static final SimpleDateFormat sf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); + + /** + * Sorts the dates in ascending order + * Returns a list of size 2 if more than 2 date fields are specified + */ + public List parseDateKeywords(List keywords) throws ParseException { + final int firstIndex = 0; + final int lastIndex = keywords.size() - 1; + + List dates = parseStringToDate(keywords); + + Collections.sort(dates); + + if (dates.size() > VALID_DATE_LIST_SIZE) { + List newKeywords = new ArrayList<>(); + newKeywords.add(dates.get(firstIndex)); + newKeywords.add(dates.get(lastIndex)); + return newKeywords; + } + + return dates; + } + + /** + * Parses a list of {@code stringDates} and returns a list of Date object + * @throws ParseException if invalid date format is supplied + */ + private List parseStringToDate(List stringsDates) throws ParseException { + List dates = new ArrayList<>(); + + for (String stringDate : stringsDates) { + try { + sf.setLenient(false); + dates.add(sf.parse(stringDate)); + } catch (java.text.ParseException pE) { + throw new ParseException(OrderDate.MESSAGE_DATE_CONSTRAINTS); + } + } + + return dates; + } +} diff --git a/src/main/java/seedu/address/logic/parser/order/OrderPredicateUtil.java b/src/main/java/seedu/address/logic/parser/order/OrderPredicateUtil.java new file mode 100644 index 000000000000..c540fae6f59f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/order/OrderPredicateUtil.java @@ -0,0 +1,158 @@ +package seedu.address.logic.parser.order; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.logic.commands.order.FindCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderAddressContainsKeywordPredicate; +import seedu.address.model.order.OrderDatePredicate; +import seedu.address.model.order.OrderFoodContainsKeywordPredicate; +import seedu.address.model.order.OrderNameContainsKeywordPredicate; +import seedu.address.model.order.OrderPhoneContainsKeywordPredicate; +import seedu.address.model.order.OrderStatus; +import seedu.address.model.order.OrderStatusContainsKeywordPredicate; + +/** + * Util to parse order's predicate + */ +public class OrderPredicateUtil { + public static final String MESSAGE_EMPTY_KEYWORD = "%1$s cannot be empty"; + + private static final String STRING_PREFIX_NAME = "n/"; + private static final String STRING_PREFIX_PHONE = "p/"; + private static final String STRING_PREFIX_ADDRESS = "a/"; + private static final String STRING_PREFIX_DATE = "dt/"; + private static final String STRING_PREFIX_FOOD = "f/"; + private static final String STRING_PREFIX_STATUS = "st/"; + + private Predicate chainedPredicated; + + /** + * Parses the given {@code argMultimap} to a chained predicate + * and returns the chained predicate + * @throws ParseException if any supplied prefix is empty + */ + public Predicate parsePredicate(ArgumentMultimap argMultimap) throws ParseException { + Set prefixes = argMultimap.getAllPrefixes(); + + for (Prefix prefix : prefixes) { + if (prefix.toString().equals("")) { + continue; + } + + List keywords = argMultimap.getAllValues(prefix); + + ensureNonEmptyField(prefix, keywords); + chainPredicate(prefix, keywords); + } + + return chainedPredicated; + } + + /** + * Ensure if prefix given has a non empty field + * @throws ParseException if one prefix is empty + */ + private void ensureNonEmptyField(Prefix prefix, List keywords) throws ParseException { + for (String keyword : keywords) { + if (keyword.equals("")) { + throw new ParseException(String.format(MESSAGE_EMPTY_KEYWORD, prefix.toString())); + } + } + } + + /** + * Parses the different {@code prefix}, forms the related predicate and chains them up + * @throws ParseException if a invalid {@code prefix} is supplied + */ + private void chainPredicate(Prefix prefix, List keywords) throws ParseException { + switch (prefix.toString()) { + case STRING_PREFIX_NAME: + String name = getLastValueFromList(keywords); + OrderNameContainsKeywordPredicate namePredicate = new OrderNameContainsKeywordPredicate(name); + + setToPredicate(namePredicate); + + break; + + case STRING_PREFIX_PHONE: + String phone = getLastValueFromList(keywords); + String[] phoneKeywords = trimAndSplitStringByWhiteSpaces(phone); + OrderPhoneContainsKeywordPredicate phonePredicate = + new OrderPhoneContainsKeywordPredicate(Arrays.asList(phoneKeywords)); + + setToPredicate(phonePredicate); + + break; + + case STRING_PREFIX_ADDRESS: + String address = getLastValueFromList(keywords).trim(); + OrderAddressContainsKeywordPredicate addressPredicate = new OrderAddressContainsKeywordPredicate(address); + + setToPredicate(addressPredicate); + + break; + + case STRING_PREFIX_DATE: + List date = new OrderDatePredicateUtil().parseDateKeywords(keywords); + OrderDatePredicate datePredicate = new OrderDatePredicate(date); + + setToPredicate(datePredicate); + + break; + + case STRING_PREFIX_FOOD: + String food = getLastValueFromList(keywords); + OrderFoodContainsKeywordPredicate foodPredicate = new OrderFoodContainsKeywordPredicate(food); + + setToPredicate(foodPredicate); + + break; + + case STRING_PREFIX_STATUS: + String status = getLastValueFromList(keywords); + String[] statusKeywords = trimAndSplitStringByWhiteSpaces(status); + List orderStatus = + new OrderStatusPredicateUtil().parseOrderStatusKeywords(Arrays.asList(statusKeywords)); + OrderStatusContainsKeywordPredicate statusPredicate = new OrderStatusContainsKeywordPredicate(orderStatus); + + setToPredicate(statusPredicate); + + break; + + default: + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + } + + private String getLastValueFromList(List list) { + int lastIndex = list.size() - 1; + return list.get(lastIndex); + } + + private String[] trimAndSplitStringByWhiteSpaces(String value) { + return value.trim().split("\\s+"); + } + + /** + * Set {@code chainedPredicate} to {@code predicate} if predicate is not set + * else AND the predicates + */ + private void setToPredicate(Predicate predicate) { + // predicate is not set + if (chainedPredicated == null) { + chainedPredicated = predicate; + } else { + chainedPredicated = chainedPredicated.and(predicate); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/order/OrderStatusPredicateUtil.java b/src/main/java/seedu/address/logic/parser/order/OrderStatusPredicateUtil.java new file mode 100644 index 000000000000..930516e5d0bc --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/order/OrderStatusPredicateUtil.java @@ -0,0 +1,33 @@ +package seedu.address.logic.parser.order; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.OrderStatus; + +/** + * Parses of status keywords and returns a valid status list + */ +public class OrderStatusPredicateUtil { + + /** + * Parses a list of {@code stringStatus} and returns a list of OrderStatus object + * @throws ParseException if invalid status is supplied + */ + public List parseOrderStatusKeywords(List stringStatuses) throws ParseException { + List statuses = new ArrayList<>(); + + for (String stringStatus : stringStatuses) { + String upperCaseStringStatus = stringStatus.toUpperCase(); + + if (!OrderStatus.isValidStatus(upperCaseStringStatus)) { + throw new ParseException(OrderStatus.MESSAGE_STATUS_CONSTRAINTS); + } + + statuses.add(new OrderStatus(upperCaseStringStatus)); + } + + return statuses; + } +} diff --git a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java b/src/main/java/seedu/address/logic/parser/order/SelectCommandParser.java similarity index 83% rename from src/main/java/seedu/address/logic/parser/SelectCommandParser.java rename to src/main/java/seedu/address/logic/parser/order/SelectCommandParser.java index 565b7f04bfe1..c58dc5a870f6 100644 --- a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/order/SelectCommandParser.java @@ -1,9 +1,11 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.order; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.order.SelectCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -14,6 +16,7 @@ public class SelectCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the SelectCommand * and returns an SelectCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public SelectCommand parse(String args) throws ParseException { 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 7f85c8b9258b..000000000000 --- 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 an 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 updatePerson(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/Model.java b/src/main/java/seedu/address/model/Model.java index ac4521f33199..66a013a3f39a 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,76 +3,234 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.deliveryman.DeliverymenList; +import seedu.address.model.order.Order; +import seedu.address.model.user.User; /** * The API of the Model component. */ public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** + * {@code Predicate} that always evaluate to true + */ + Predicate PREDICATE_SHOW_ALL_ORDERS = unused -> true; + + /** + * {@code Predicate} that always evaluate to true + */ + Predicate PREDICATE_SHOW_ALL_USERS = unused -> true; + /** + * {@code Predicate} that always evaluates to true + */ + Predicate PREDICATE_SHOW_ALL_DELIVERYMEN = unused -> true; + + // ==================== order book/order related methods ======================= - /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); + /** + * Clears existing backing model and replaces with the provided new data. + */ + void resetData(ReadOnlyOrderBook newData); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + /** + * Returns the OrderBook + */ + ReadOnlyOrderBook getOrderBook(); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a common with the same identity as {@code order} exists in the order book. */ - boolean hasPerson(Person person); + boolean hasOrder(Order order); /** - * Deletes the given person. - * The person must exist in the address book. + * Deletes the given order. + * The order must exist in the address book. */ - void deletePerson(Person target); + void deleteOrder(Order target); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Adds the given order. + * {@code order} must not already exist in the address book. */ - void addPerson(Person person); + void addOrder(Order order); /** - * Replaces the given person {@code target} with {@code editedPerson}. + * Replaces the given order {@code target} with {@code editedOrder}. * {@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 order identity of {@code editedOrder} must not be the same as another existing order in the order book. */ - void updatePerson(Person target, Person editedPerson); + void updateOrder(Order target, Order editedOrder); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** + * Returns an unmodifiable view of the filtered order list + */ + ObservableList getFilteredOrderList(); + + /** + * Updates the filter of the filtered order list to filter by the given {@code predicate}. + * + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredOrderList(Predicate predicate); + + /** + * Returns true if the model has previous order book states to restore. + */ + boolean canUndoOrderBook(); + + /** + * Returns true if the model has undone order book states to restore. + */ + boolean canRedoOrderBook(); + + /** + * Restores the model's order book to its previous state. + */ + void undoOrderBook(); + + /** + * Restores the model's order book to its previously undone state. + */ + void redoOrderBook(); + + /** + * Saves the current order book state for undo/redo. + */ + void commitOrderBook(); + + // ======================== User related methods ========================= + + /** + * Return true if user exist inside user list. + */ + boolean hasUser(User user); + + /** + * Add user to usersList. + */ + void addUser(User user); + + /** + * Saves the current users list for undo/redo. + */ + void commitUsersList(); + + /** + * Updates the filter of the filtered user list to filter by the given {@code predicate}. + * + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredUsersList(Predicate predicate); + + + /** + * Returns an unmodifiable view of the filtered users list + */ + ObservableList getFilteredUsersList(); + + /** + * Return true if user is registered with application. + */ + boolean isRegisteredUser(User user); + + /** + * Returns the UsersList + */ + ReadOnlyUsersList getUsersList(); + + + /** + * Return true if user is logged into FoodZoom. + */ + boolean isUserLoggedIn(); + + /** + * Store the user session details. + */ + void storeUserInSession(User user); + + /** + * Return the logged in user details. + */ + User getLoggedInUserDetails(); + + /** + * Clear the user session details. + */ + void clearUserInSession(); + + // ======================== Deliveryman related methods ========================= + + /** + * Clears existing backing model and replaces with the provided new data. + */ + void resetDeliverymenData(DeliverymenList newData); + + /** + * Returns the DeliverymenList + */ + DeliverymenList getDeliverymenList(); + + /** + * Returns true if a deliveryman with the same identity as {@code deliveryman} exists in the address book. + */ + boolean hasDeliveryman(Deliveryman deliveryman); + + /** + * Deletes the given deliveryman. + * The deliveryman must exist in the address book. + */ + void deleteDeliveryman(Deliveryman target); + + /** + * Adds the given deliveryman. + * {@code deliveryman} must not already exist in the address book. + */ + void addDeliveryman(Deliveryman deliveryman); + + /** + * Replaces the given deliveryman {@code target} with {@code editedDeliveryman}. + * {@code target} must exist in the address book. + * The deliveryman identity of {@code editedDeliveryman} must not be the same as another + * existing deliveryman in the deliverymen list. + */ + void updateDeliveryman(Deliveryman target, Deliveryman editedDeliveryman); + + /** + * Returns an unmodifiable view of the filtered deliveryman list + */ + ObservableList getFilteredDeliverymenList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the filter of the filtered deliveryman list to filter by the given {@code predicate}. + * * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredDeliverymenList(Predicate predicate); /** * Returns true if the model has previous address book states to restore. */ - boolean canUndoAddressBook(); + boolean canUndoDeliverymenList(); /** * Returns true if the model has undone address book states to restore. */ - boolean canRedoAddressBook(); + boolean canRedoDeliverymenList(); /** * Restores the model's address book to its previous state. */ - void undoAddressBook(); + void undoDeliverymenList(); /** * Restores the model's address book to its previously undone state. */ - void redoAddressBook(); + void redoDeliverymenList(); /** * Saves the current address book state for undo/redo. */ - void commitAddressBook(); + void commitDeliverymenList(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index a664602ef5b1..a434abd052ec 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -10,9 +10,19 @@ import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.ComponentManager; +import seedu.address.commons.core.EventsCenter; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.model.person.Person; +import seedu.address.commons.events.model.FoodZoomChangedEvent; +import seedu.address.commons.events.model.UserLoggedInEvent; +import seedu.address.commons.events.model.UserLoggedOutEvent; +import seedu.address.commons.events.model.UsersListChangedEvent; +import seedu.address.commons.events.ui.BackToHomeEvent; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.deliveryman.DeliverymenList; +import seedu.address.model.deliveryman.VersionedDeliverymenList; +import seedu.address.model.order.Order; +import seedu.address.model.user.User; +import seedu.address.model.user.UserSession; /** * Represents the in-memory model of the address book data. @@ -20,113 +30,308 @@ public class ModelManager extends ComponentManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final VersionedAddressBook versionedAddressBook; - private final FilteredList filteredPersons; + private final VersionedOrderBook versionedOrderBook; + private final VersionedUsersList versionedUsersList; + private final FilteredList filteredOrders; + private final FilteredList filteredUsers; + + private final VersionedDeliverymenList versionedDeliverymenList; + private final FilteredList filteredDeliverymen; + + private final UserSession userSession; /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * Initializes a ModelManager with the given addressBook, usersList and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { + public ModelManager(ReadOnlyOrderBook orderBook, ReadOnlyUsersList usersList, + DeliverymenList deliverymenList, UserPrefs userPrefs) { super(); - requireAllNonNull(addressBook, userPrefs); + requireAllNonNull(orderBook, userPrefs, deliverymenList); + + logger.fine("Initializing with order book: " + orderBook + + " and users list " + usersList + + " and deliverymen list " + deliverymenList + + " and user prefs " + userPrefs); + + versionedOrderBook = new VersionedOrderBook(orderBook); + versionedUsersList = new VersionedUsersList(usersList); + versionedDeliverymenList = new VersionedDeliverymenList(deliverymenList); + filteredOrders = new FilteredList<>(versionedOrderBook.getOrderList()); + filteredUsers = new FilteredList<>(versionedUsersList.getUserList()); + filteredDeliverymen = new FilteredList<>(versionedDeliverymenList.getDeliverymenList()); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + userSession = new UserSession(); - versionedAddressBook = new VersionedAddressBook(addressBook); - filteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); + logger.fine("Initializing with order book: " + orderBook + + " and users list " + + usersList + + " and deliverymen list" + + deliverymenList + + " and user prefs " + + userPrefs); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new OrderBook(), new UsersList(), new DeliverymenList(), new UserPrefs()); } @Override - public void resetData(ReadOnlyAddressBook newData) { - versionedAddressBook.resetData(newData); - indicateAddressBookChanged(); + public void resetData(ReadOnlyOrderBook newData) { + versionedOrderBook.resetData(newData); + indicateAppChanged(); } @Override - public ReadOnlyAddressBook getAddressBook() { - return versionedAddressBook; + public void resetDeliverymenData(DeliverymenList newData) { + versionedDeliverymenList.resetData(newData); + indicateAppChanged(); } - /** Raises an event to indicate the model has changed */ - private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(versionedAddressBook)); + @Override + public ReadOnlyOrderBook getOrderBook() { + return versionedOrderBook; } @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return versionedAddressBook.hasPerson(person); + public DeliverymenList getDeliverymenList() { + return versionedDeliverymenList; + } + + /** Raises an event to indicate that there is an app change. */ + private void indicateAppChanged() { + raise(new FoodZoomChangedEvent(versionedOrderBook, versionedDeliverymenList)); + } + + /** + * Raises an event to indicate the model has changed + */ + private void indicateUsersListChanged() { + raise(new UsersListChangedEvent(versionedUsersList)); + } + + /** + * Raises an event to indicate user have logged in. + */ + private void indicateUserLoggedIn(User user) { + raise(new UserLoggedInEvent(user)); + } + + /** + * Raises an event to indicate user have logged out. + */ + private void indicateUserLoggedOut() { + raise(new UserLoggedOutEvent()); } @Override - public void deletePerson(Person target) { - versionedAddressBook.removePerson(target); - indicateAddressBookChanged(); + public boolean hasOrder(Order order) { + requireNonNull(order); + return versionedOrderBook.hasOrder(order); } @Override - public void addPerson(Person person) { - versionedAddressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - indicateAddressBookChanged(); + public void deleteOrder(Order target) { + versionedOrderBook.removeOrder(target); + indicateAppChanged(); } @Override - public void updatePerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public void addOrder(Order order) { + versionedOrderBook.addOrder(order); + updateFilteredOrderList(PREDICATE_SHOW_ALL_ORDERS); + indicateAppChanged(); + } + + @Override + public void updateOrder(Order target, Order editedOrder) { + requireAllNonNull(target, editedOrder); - versionedAddressBook.updatePerson(target, editedPerson); - indicateAddressBookChanged(); + versionedOrderBook.updateOrder(target, editedOrder); + indicateAppChanged(); } - //=========== Filtered Person List Accessors ============================================================= + // =========== Deliveryman methods ==================================== + + @Override + public boolean hasDeliveryman(Deliveryman deliveryman) { + requireNonNull(deliveryman); + return versionedDeliverymenList.hasDeliveryman(deliveryman); + } + + @Override + public void deleteDeliveryman(Deliveryman target) { + versionedDeliverymenList.removeDeliveryman(target); + indicateAppChanged(); + } + + @Override + public void addDeliveryman(Deliveryman deliveryman) { + versionedDeliverymenList.addDeliveryman(deliveryman); + indicateAppChanged(); + } + + @Override + public void updateDeliveryman(Deliveryman target, Deliveryman editedDeliveryman) { + requireAllNonNull(target, editedDeliveryman); + + versionedDeliverymenList.updateDeliveryman(target, editedDeliveryman); + indicateAppChanged(); + } + + //=========== Filtered Orders List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Order} backed by the internal list of + * {@code versionedOrderBook} + */ + @Override + public ObservableList getFilteredOrderList() { + return FXCollections.unmodifiableObservableList(filteredOrders); + } + + @Override + public void updateFilteredOrderList(Predicate predicate) { + requireNonNull(predicate); + filteredOrders.setPredicate(predicate); + EventsCenter.getInstance().post(new BackToHomeEvent()); + } + + //=========== Filtered Deliveryman List Accessors ======================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} + * Returns an unmodifiable view of the list of {@code Deliveryman} backed by the internal list + * of {@code versionedDeliverymenList} */ @Override - public ObservableList getFilteredPersonList() { - return FXCollections.unmodifiableObservableList(filteredPersons); + public ObservableList getFilteredDeliverymenList() { + return FXCollections.unmodifiableObservableList(filteredDeliverymen); } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredDeliverymenList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredDeliverymen.setPredicate(predicate); + EventsCenter.getInstance().post(new BackToHomeEvent()); } //=========== Undo/Redo ================================================================================= @Override - public boolean canUndoAddressBook() { - return versionedAddressBook.canUndo(); + public boolean canUndoOrderBook() { + return versionedOrderBook.canUndo(); } @Override - public boolean canRedoAddressBook() { - return versionedAddressBook.canRedo(); + public boolean canRedoOrderBook() { + return versionedOrderBook.canRedo(); } @Override - public void undoAddressBook() { - versionedAddressBook.undo(); - indicateAddressBookChanged(); + public void undoOrderBook() { + versionedOrderBook.undo(); + indicateAppChanged(); } @Override - public void redoAddressBook() { - versionedAddressBook.redo(); - indicateAddressBookChanged(); + public void redoOrderBook() { + versionedOrderBook.redo(); + indicateAppChanged(); } @Override - public void commitAddressBook() { - versionedAddressBook.commit(); + public void commitOrderBook() { + versionedOrderBook.commit(); + } + + + //=========== Filtered User List Accessors ============================================================= + @Override + public boolean hasUser(User user) { + requireNonNull(user); + return versionedUsersList.hasUser(user); + } + + @Override + public void addUser(User user) { + versionedUsersList.addUser(user); + updateFilteredUsersList(PREDICATE_SHOW_ALL_USERS); + indicateUsersListChanged(); + } + + @Override + public void commitUsersList() { + versionedUsersList.commit(); + } + + @Override + public void updateFilteredUsersList(Predicate predicate) { + requireNonNull(predicate); + filteredUsers.setPredicate(predicate); + } + + @Override + public ObservableList getFilteredUsersList() { + return FXCollections.unmodifiableObservableList(filteredUsers); + } + + @Override + public boolean isRegisteredUser(User user) { + requireNonNull(user); + return versionedUsersList.isRegisteredUser(user); + } + + @Override + public ReadOnlyUsersList getUsersList() { + return versionedUsersList; + } + + @Override + public boolean isUserLoggedIn() { + return userSession.isUserAlreadyLoggedIn(); + } + + @Override + public void storeUserInSession(User user) { + userSession.setUserSession(user); + indicateUserLoggedIn(user); + } + + @Override + public User getLoggedInUserDetails() { + return userSession.getLoggedInUserDetails(); + } + + @Override + public void clearUserInSession() { + userSession.clearUserSession(); + indicateUserLoggedOut(); + } + + @Override + public boolean canUndoDeliverymenList() { + return versionedDeliverymenList.canUndo(); + } + + @Override + public boolean canRedoDeliverymenList() { + return versionedDeliverymenList.canRedo(); + } + + @Override + public void undoDeliverymenList() { + versionedDeliverymenList.undo(); + indicateAppChanged(); + } + + @Override + public void redoDeliverymenList() { + versionedDeliverymenList.redo(); + indicateAppChanged(); + } + + @Override + public void commitDeliverymenList() { + versionedDeliverymenList.commit(); } @Override @@ -143,8 +348,13 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; - return versionedAddressBook.equals(other.versionedAddressBook) - && filteredPersons.equals(other.filteredPersons); + return versionedOrderBook.equals(other.versionedOrderBook) + && filteredOrders.equals(other.filteredOrders) + && versionedUsersList.equals(other.versionedUsersList) + && filteredUsers.equals(other.filteredUsers) + && versionedDeliverymenList.equals(other.versionedDeliverymenList) + && filteredDeliverymen.equals(other.filteredDeliverymen); } + } diff --git a/src/main/java/seedu/address/model/OrderBook.java b/src/main/java/seedu/address/model/OrderBook.java new file mode 100644 index 000000000000..391c7fd9f874 --- /dev/null +++ b/src/main/java/seedu/address/model/OrderBook.java @@ -0,0 +1,128 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.model.order.Order; +import seedu.address.model.order.UniqueOrderList; + + +/** + * Wraps all data at the address-book level + * Duplicates are not allowed (by .isSamePerson comparison) + */ +public class OrderBook implements ReadOnlyOrderBook { + + private final UniqueOrderList orders; + + /* + * The 'unusual' code block below is an 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. + */ + { + orders = new UniqueOrderList(); + } + + public OrderBook() { + } + + /** + * Creates an OrderBook using the Orders in the {@code toBeCopied} + */ + public OrderBook(ReadOnlyOrderBook toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the order list with {@code orders}. + * {@code orders} must not contain duplicate orders. + */ + public void setOrders(List orders) { + this.orders.setOrder(orders); + } + + /** + * Resets the existing data of this {@code OrderBook} with {@code newData}. + */ + public void resetData(ReadOnlyOrderBook newData) { + requireNonNull(newData); + + setOrders(newData.getOrderList()); + } + + //// common-level operations + + /** + * Returns true if an order with the same identity as {@code order} exists in the order book. + */ + public boolean hasOrder(Order person) { + requireNonNull(person); + return orders.contains(person); + } + + /** + * Adds an order to the order book. + * The order must not already exist in the order book. + */ + public void addOrder(Order o) { + if (o.getTag() == null) { + o.assignTag(); + } + orders.add(o); + } + + /** + * Replaces the given order {@code target} in the list with {@code editedOrder}. + * {@code target} must exist in the order book. + * The order identity of {@code editedOrder} must not be the same as another existing order in the order book. + */ + public void updateOrder(Order target, Order editedOrder) { + requireNonNull(editedOrder); + + if (editedOrder.getTag() == null) { + editedOrder.assignTag(); + } + orders.setOrder(target, editedOrder); + } + + /** + * Removes {@code key} from this {@code OrderBook}. + * {@code key} must exist in the address book. + */ + public void removeOrder(Order key) { + orders.remove(key); + } + + //// util methods + + @Override + public String toString() { + return orders.asUnmodifiableObservableList().size() + " orders"; + // TODO: refine later + } + + @Override + public ObservableList getOrderList() { + return orders.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderBook // instanceof handles nulls + && orders.equals(((OrderBook) other).orders)); + } + + @Override + public int hashCode() { + return orders.hashCode(); + } +} 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 6ddc2cd9a290..000000000000 --- 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/ReadOnlyOrderBook.java b/src/main/java/seedu/address/model/ReadOnlyOrderBook.java new file mode 100644 index 000000000000..7ecd089c37d4 --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyOrderBook.java @@ -0,0 +1,17 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.order.Order; + +/** + * Unmodifiable view of an order book + */ +public interface ReadOnlyOrderBook { + + /** + * Returns an unmodifiable view of the orders list. + * This list will not contain any duplicate orders. + */ + ObservableList getOrderList(); + +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUsersList.java b/src/main/java/seedu/address/model/ReadOnlyUsersList.java new file mode 100644 index 000000000000..b6c137c1ede3 --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyUsersList.java @@ -0,0 +1,16 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.user.User; + +/** + * Unmodifiable view of users + */ +public interface ReadOnlyUsersList { + + /** + * Returns an unmodifiable view of the user list. + * This list will not contain any duplicate users. + */ + ObservableList getUserList(); +} diff --git a/src/main/java/seedu/address/model/TaggedObject.java b/src/main/java/seedu/address/model/TaggedObject.java new file mode 100644 index 000000000000..c0c01b7a754a --- /dev/null +++ b/src/main/java/seedu/address/model/TaggedObject.java @@ -0,0 +1,32 @@ +package seedu.address.model; + +import java.util.UUID; + +/** + * Represents the Objects that need an ID for storage & reference + */ +public abstract class TaggedObject { + public static final String MESSAGE_INVALID_ID = "Not an valid ID! Please check XML File."; + + private UUID tag; + + protected TaggedObject() { + tag = null; + } + + protected TaggedObject(UUID id) { + this.tag = id; + } + + public UUID getTag() { + return tag; + } + + public void assignTag() { + tag = UUID.randomUUID(); + } + + public boolean hasSameTag(TaggedObject other) { + return getTag().equals(other.getTag()); + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 980b2b388852..0d8b0b6ecde1 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -12,7 +12,11 @@ public class UserPrefs { private GuiSettings guiSettings; - private Path addressBookFilePath = Paths.get("data" , "addressbook.xml"); + private Path foodZoomFilePath = Paths.get("data" , "addressbook.xml"); + private Path routeListFilePath = Paths.get("data", "routelist.xml"); + private Path usersListFilePath = Paths.get("data", "users.xml"); + + private Path deliverymenListFilePath = Paths.get("data" , "deliverymen.xml"); public UserPrefs() { setGuiSettings(500, 500, 0, 0); @@ -30,12 +34,35 @@ public void setGuiSettings(double width, double height, int x, int y) { guiSettings = new GuiSettings(width, height, x, y); } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getFoodZoomFilePath() { + return foodZoomFilePath; + } + + public void setFoodZoomFilePath(Path foodZoomFilePath) { + this.foodZoomFilePath = foodZoomFilePath; + } + + public Path getRouteListFilePath() { + return routeListFilePath; + } + + public void setRouteListFilePath(Path routeListFilePath) { + this.routeListFilePath = routeListFilePath; + } + + public Path getUsersListFilePath() { + return usersListFilePath; + } + + public void setUsersListFilePath(Path usersListFilePath) { + this.usersListFilePath = usersListFilePath; + } + public Path getDeliverymenListFilePath() { + return deliverymenListFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - this.addressBookFilePath = addressBookFilePath; + public void setDeliverymenListFilePath(Path deliverymenListFilePath) { + this.deliverymenListFilePath = deliverymenListFilePath; } @Override @@ -50,19 +77,20 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return Objects.equals(guiSettings, o.guiSettings) - && Objects.equals(addressBookFilePath, o.addressBookFilePath); + && Objects.equals(foodZoomFilePath, o.foodZoomFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, foodZoomFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings.toString()); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + foodZoomFilePath); + sb.append("\nLocal data file location : " + usersListFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/model/UsersList.java b/src/main/java/seedu/address/model/UsersList.java new file mode 100644 index 000000000000..e8d2501f5466 --- /dev/null +++ b/src/main/java/seedu/address/model/UsersList.java @@ -0,0 +1,119 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.model.user.UniqueUserList; +import seedu.address.model.user.User; + +/** + * Wraps all data at the users list. + * Duplicates are not allowed. + */ +public class UsersList implements ReadOnlyUsersList { + + private final UniqueUserList users; + + { + users = new UniqueUserList(); + } + + public UsersList() { + } + + /** + * Creates an UsersList using the User in the {@code toBeCopied} + */ + public UsersList(ReadOnlyUsersList toBeCopied) { + this(); + resetData(toBeCopied); + } + + /** + * Replaces the contents of the user list with {@code users}. + * {@code persons} must not contain duplicate user. + */ + public void setUsers(List users) { + this.users.setUsers(users); + } + + /** + * Resets the existing data of this {@code UsersList} with {@code newData}. + */ + public void resetData(ReadOnlyUsersList newData) { + requireNonNull(newData); + setUsers(newData.getUserList()); + } + + /** + * Returns true if a user with the same identity as {@code user} exists in the usersList. + */ + public boolean hasUser(User user) { + requireNonNull(user); + return users.contains(user); + } + + /** + * Returns true if a user with the same identity as {@code user} exists in the usersList. + */ + public boolean isRegisteredUser(User user) { + requireNonNull(user); + return users.check(user); + } + + /** + * Adds a user to the user list. + * The user must not already exist in the user list. + */ + public void addUser(User u) { + users.add(u); + } + + /** + * Replaces the given common {@code target} in the list with {@code editedUser}. + * {@code target} must exist in the users list. + * The common identity of {@code editedPerson} must not be the same as another existing user in the users list. + */ + public void updateUser(User target, User editedUser) { + requireNonNull(editedUser); + users.setUser(target, editedUser); + } + + /** + * Removes {@code key} from this {@code UsersList}. + * {@code key} must exist in the userslist. + */ + public void removeUser(User key) { + users.remove(key); + } + + //// util methods + + @Override + public String toString() { + return users.asUnmodifiableObservableList().size() + " users"; + // TODO: refine later + } + + @Override + public ObservableList getUserList() { + return users.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UsersList // instanceof handles nulls + && users.equals(((UsersList) other).users)); + } + + @Override + public int hashCode() { + return users.hashCode(); + } + +} + + diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedOrderBook.java similarity index 57% rename from src/main/java/seedu/address/model/VersionedAddressBook.java rename to src/main/java/seedu/address/model/VersionedOrderBook.java index 227a335045d7..77f477f61ae4 100644 --- a/src/main/java/seedu/address/model/VersionedAddressBook.java +++ b/src/main/java/seedu/address/model/VersionedOrderBook.java @@ -4,33 +4,33 @@ import java.util.List; /** - * {@code AddressBook} that keeps track of its own history. + * {@code OrderBook} that keeps track of its own history. */ -public class VersionedAddressBook extends AddressBook { +public class VersionedOrderBook extends OrderBook { - private final List addressBookStateList; + private final List orderBookStateList; private int currentStatePointer; - public VersionedAddressBook(ReadOnlyAddressBook initialState) { + public VersionedOrderBook(ReadOnlyOrderBook initialState) { super(initialState); - addressBookStateList = new ArrayList<>(); - addressBookStateList.add(new AddressBook(initialState)); + orderBookStateList = new ArrayList<>(); + orderBookStateList.add(new OrderBook(initialState)); currentStatePointer = 0; } /** - * Saves a copy of the current {@code AddressBook} state at the end of the state list. + * Saves a copy of the current {@code OrderBook} state at the end of the state list. * Undone states are removed from the state list. */ public void commit() { removeStatesAfterCurrentPointer(); - addressBookStateList.add(new AddressBook(this)); + orderBookStateList.add(new OrderBook(this)); currentStatePointer++; } private void removeStatesAfterCurrentPointer() { - addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear(); + orderBookStateList.subList(currentStatePointer + 1, orderBookStateList.size()).clear(); } /** @@ -41,7 +41,7 @@ public void undo() { throw new NoUndoableStateException(); } currentStatePointer--; - resetData(addressBookStateList.get(currentStatePointer)); + resetData(orderBookStateList.get(currentStatePointer)); } /** @@ -52,7 +52,7 @@ public void redo() { throw new NoRedoableStateException(); } currentStatePointer++; - resetData(addressBookStateList.get(currentStatePointer)); + resetData(orderBookStateList.get(currentStatePointer)); } /** @@ -66,7 +66,7 @@ public boolean canUndo() { * Returns true if {@code redo()} has address book states to redo. */ public boolean canRedo() { - return currentStatePointer < addressBookStateList.size() - 1; + return currentStatePointer < orderBookStateList.size() - 1; } @Override @@ -77,16 +77,16 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof VersionedAddressBook)) { + if (!(other instanceof VersionedOrderBook)) { return false; } - VersionedAddressBook otherVersionedAddressBook = (VersionedAddressBook) other; + VersionedOrderBook otherVersionedOrderBook = (VersionedOrderBook) other; // state check - return super.equals(otherVersionedAddressBook) - && addressBookStateList.equals(otherVersionedAddressBook.addressBookStateList) - && currentStatePointer == otherVersionedAddressBook.currentStatePointer; + return super.equals(otherVersionedOrderBook) + && orderBookStateList.equals(otherVersionedOrderBook.orderBookStateList) + && currentStatePointer == otherVersionedOrderBook.currentStatePointer; } /** @@ -94,7 +94,7 @@ public boolean equals(Object other) { */ public static class NoUndoableStateException extends RuntimeException { private NoUndoableStateException() { - super("Current state pointer at start of addressBookState list, unable to undo."); + super("Current state pointer at start of orderBookState list, unable to undo."); } } @@ -103,7 +103,7 @@ private NoUndoableStateException() { */ public static class NoRedoableStateException extends RuntimeException { private NoRedoableStateException() { - super("Current state pointer at end of addressBookState list, unable to redo."); + super("Current state pointer at end of orderBookState list, unable to redo."); } } } diff --git a/src/main/java/seedu/address/model/VersionedUsersList.java b/src/main/java/seedu/address/model/VersionedUsersList.java new file mode 100644 index 000000000000..52c25e75786c --- /dev/null +++ b/src/main/java/seedu/address/model/VersionedUsersList.java @@ -0,0 +1,109 @@ +package seedu.address.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@code UsersList} that keeps track of its own history. + */ +public class VersionedUsersList extends UsersList { + + private final List usersListState; + private int currentStatePointer; + + public VersionedUsersList(ReadOnlyUsersList initialState) { + super(initialState); + + usersListState = new ArrayList<>(); + usersListState.add(new UsersList(initialState)); + currentStatePointer = 0; + } + + /** + * Saves a copy of the current {@code UsersList} state at the end of the state list. + * Undone states are removed from the state list. + */ + public void commit() { + removeStatesAfterCurrentPointer(); + usersListState.add(new UsersList(this)); + currentStatePointer++; + } + + private void removeStatesAfterCurrentPointer() { + usersListState.subList(currentStatePointer + 1, usersListState.size()).clear(); + } + + + /** + * Restores the users list to its previous state. + */ + public void undo() { + if (!canUndo()) { + throw new NoUndoableStateException(); + } + currentStatePointer--; + resetData(usersListState.get(currentStatePointer)); + } + + /** + * Restores the users list to its previously undone state. + */ + public void redo() { + if (!canRedo()) { + throw new NoRedoableStateException(); + } + currentStatePointer++; + resetData(usersListState.get(currentStatePointer)); + } + + /** + * Returns true if {@code undo()} has users list states to undo. + */ + public boolean canUndo() { + return currentStatePointer > 0; + } + + /** + * Returns true if {@code redo()} has users list states to redo. + */ + public boolean canRedo() { + return currentStatePointer < usersListState.size() - 1; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof VersionedUsersList)) { + return false; + } + + VersionedUsersList otherVersionedUsersList = (VersionedUsersList) other; + + // state check + return super.equals(otherVersionedUsersList) + && usersListState.equals(otherVersionedUsersList.usersListState) + && currentStatePointer == otherVersionedUsersList.currentStatePointer; + } + /** + * Thrown when trying to {@code undo()} but can't. + */ + public static class NoUndoableStateException extends RuntimeException { + private NoUndoableStateException() { + super("Current state pointer at start of usersListState list, unable to undo."); + } + } + + /** + * Thrown when trying to {@code redo()} but can't. + */ + public static class NoRedoableStateException extends RuntimeException { + private NoRedoableStateException() { + super("Current state pointer at end of usersListState list, unable to redo."); + } + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/common/Address.java similarity index 75% rename from src/main/java/seedu/address/model/person/Address.java rename to src/main/java/seedu/address/model/common/Address.java index a1409233ceb9..18347cb6767b 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/common/Address.java @@ -1,8 +1,11 @@ -package seedu.address.model.person; +package seedu.address.model.common; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * Represents a Person's address in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} @@ -10,13 +13,14 @@ public class Address { public static final String MESSAGE_ADDRESS_CONSTRAINTS = - "Addresses can take any values, and it should not be blank"; + "Addresses can take any values, and it should not be blank. The last 6 characters should be a valid " + + "postal code"; /* * The first character of the address must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ - public static final String ADDRESS_VALIDATION_REGEX = "[^\\s].*"; + public static final String ADDRESS_VALIDATION_REGEX = "[^\\s].*(\\d{6})$"; public final String value; @@ -38,6 +42,18 @@ public static boolean isValidAddress(String test) { return test.matches(ADDRESS_VALIDATION_REGEX); } + /* + * Returns the postal code form this address + */ + public String getPostalCode() { + Pattern pattern = Pattern.compile(ADDRESS_VALIDATION_REGEX); + Matcher matcher = pattern.matcher(value); + + matcher.matches(); + + return matcher.group(1); + } + @Override public String toString() { return value; diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/common/Name.java similarity index 85% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/common/Name.java index 9982393dabb5..084763b87b38 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/common/Name.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.common; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; @@ -10,13 +10,14 @@ public class Name { public static final String MESSAGE_NAME_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Names should only contain alphanumeric characters and spaces, it should not be blank " + + "and be 3 to 64 characters long."; /* * The first character of the address must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ - public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]{1,64}"; public final String fullName; @@ -48,7 +49,7 @@ public String toString() { public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check + && fullName.equalsIgnoreCase(((Name) other).fullName)); // state check } @Override diff --git a/src/main/java/seedu/address/model/common/Password.java b/src/main/java/seedu/address/model/common/Password.java new file mode 100644 index 000000000000..36d9bd7cf76d --- /dev/null +++ b/src/main/java/seedu/address/model/common/Password.java @@ -0,0 +1,58 @@ +package seedu.address.model.common; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Person's password in FoodZoom. + * Guarantees: immutable; is valid as declared in {@link #isValidPassword(String)} + */ +public class Password { + + public static final String MESSAGE_PASSWORD_CONSTRAINTS = + "Password should only contain alphanumeric characters , should be at least 6 characters " + + "and it should not be blank"; + + /* + * The first character of the password must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String PASSWORD_VALIDATION_REGEX = "[\\p{Alnum}]{6,}"; + public final String value; + + + /** + * Constructs a {@code Password}. + * + * @param password A valid password. + */ + public Password(String password) { + requireNonNull(password); + isValidPassword(password); + value = password; + } + + + /** + * Returns true if a given string is a valid password. + */ + public static boolean isValidPassword(String test) { + return test.matches(PASSWORD_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Password // instanceof handles nulls + && value.equals(((Password) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/common/Phone.java similarity index 97% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/address/model/common/Phone.java index a22e51653835..9bdd3b090c25 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/common/Phone.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.common; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; diff --git a/src/main/java/seedu/address/model/common/Username.java b/src/main/java/seedu/address/model/common/Username.java new file mode 100644 index 000000000000..d2bf7647a16a --- /dev/null +++ b/src/main/java/seedu/address/model/common/Username.java @@ -0,0 +1,58 @@ +package seedu.address.model.common; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Person's username in FoodZoom. + * Guarantees: immutable; is valid as declared in {@link #isValidUsername(String)} + */ +public class Username { + + public static final String MESSAGE_USERNAME_CONSTRAINTS = + "Username should only contain alphanumeric characters, it should not be blank " + + "and be 3 to 64 characters long."; + + /* + * The first character of the username must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String USERNAME_VALIDATION_REGEX = "[\\p{Alnum}]{3,64}"; + public final String value; + + + /** + * Constructs a {@code Username}. + * + * @param username A valid username. + */ + public Username(String username) { + requireNonNull(username); + isValidUsername(username); + value = username; + } + + + /** + * Returns true if a given string is a valid username. + */ + public static boolean isValidUsername(String test) { + return test.matches(USERNAME_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Username // instanceof handles nulls + && value.equals(((Username) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/common/exceptions/DuplicatePersonException.java similarity index 87% rename from src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java rename to src/main/java/seedu/address/model/common/exceptions/DuplicatePersonException.java index d7290f594423..8638d811b07e 100644 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ b/src/main/java/seedu/address/model/common/exceptions/DuplicatePersonException.java @@ -1,4 +1,4 @@ -package seedu.address.model.person.exceptions; +package seedu.address.model.common.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/common/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/common/exceptions/PersonNotFoundException.java new file mode 100644 index 000000000000..cf3ee4af456e --- /dev/null +++ b/src/main/java/seedu/address/model/common/exceptions/PersonNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.address.model.common.exceptions; + +/** + * Signals that the operation is unable to find the specified common. + */ +public class PersonNotFoundException extends RuntimeException { +} diff --git a/src/main/java/seedu/address/model/deliveryman/Deliveryman.java b/src/main/java/seedu/address/model/deliveryman/Deliveryman.java new file mode 100644 index 000000000000..ac0086fc0a98 --- /dev/null +++ b/src/main/java/seedu/address/model/deliveryman/Deliveryman.java @@ -0,0 +1,125 @@ +package seedu.address.model.deliveryman; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +import seedu.address.model.TaggedObject; +import seedu.address.model.common.Name; +import seedu.address.model.deliveryman.exceptions.OrdersLimitExceededException; +import seedu.address.model.order.Order; + +/** + * Represents a Deliveryman in FoodZoom. + * Guarantees: has a name that is unique. + */ +public class Deliveryman extends TaggedObject { + + /** Limit for amount of orders a deliveryman can have at one point of time */ + public static final int ORDERS_LIMIT = 5; + + private final Name name; + private final Set orders = new HashSet<>(); + + public Deliveryman(Name name) { + this(null, name, null); + } + + public Deliveryman(Name name, Set orders) { + this(null, name, orders); + } + + /** This constructor is used when the {@code id} is specified. */ + public Deliveryman(UUID id, Name name, Set orders) { + super(id); + requireAllNonNull(name); + this.name = name; + if (orders != null) { + this.orders.addAll(orders); + } + } + + /** + * This constructor is used to create a new copy of {@code deliveryman}. + */ + public Deliveryman(Deliveryman deliveryman) { + this(null, deliveryman.name, deliveryman.orders); + } + + public Name getName() { + return name; + } + + public Set getOrders() { + return orders; + } + + /** + * Adds {@code order} to the set of orders for the deliveryman. + * Throws {@code OrdersLimitExceededException} if the amount of orders assigned exceeds the limit for orders. + */ + public void addOrder(Order order) throws OrdersLimitExceededException { + if (orders.size() >= ORDERS_LIMIT) { + throw new OrdersLimitExceededException(); + } + orders.add(order); + } + + public boolean canAccommodate(Collection orders) { + return getOrders().size() + orders.size() <= ORDERS_LIMIT; + } + + public void removeOrder(Order order) { + orders.remove(order); + } + + public boolean hasOrders() { + return !orders.isEmpty(); + } + + /** + * Returns if this is the same common as {@code other} + */ + public boolean isSameDeliveryman(Deliveryman other) { + if (other == this) { + return true; + } + + return other != null && other.getName().equals(getName()); + } + + /** + * Returns true if both deliverymen have the same identity and data fields. + * This defines a stronger notion of equality between two persons. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Deliveryman)) { + return false; + } + + Deliveryman otherPerson = (Deliveryman) other; + return otherPerson.getName().equals(getName()); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/deliveryman/DeliverymanNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/deliveryman/DeliverymanNameContainsKeywordsPredicate.java new file mode 100644 index 000000000000..ef4cb926cd9f --- /dev/null +++ b/src/main/java/seedu/address/model/deliveryman/DeliverymanNameContainsKeywordsPredicate.java @@ -0,0 +1,26 @@ +package seedu.address.model.deliveryman; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Deliveryman}'s {@code Name} matches any of the keywords given. + */ +public class DeliverymanNameContainsKeywordsPredicate implements Predicate { + private final String keyword; + + public DeliverymanNameContainsKeywordsPredicate(String keyword) { + this.keyword = keyword.trim().toLowerCase(); + } + + @Override + public boolean test(Deliveryman deliveryman) { + return deliveryman.getName().fullName.toLowerCase().contains(keyword); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeliverymanNameContainsKeywordsPredicate // instanceof handles nulls + && keyword.equals(((DeliverymanNameContainsKeywordsPredicate) other).keyword)); // state check + } +} diff --git a/src/main/java/seedu/address/model/deliveryman/DeliverymenList.java b/src/main/java/seedu/address/model/deliveryman/DeliverymenList.java new file mode 100644 index 000000000000..4ae3024d088e --- /dev/null +++ b/src/main/java/seedu/address/model/deliveryman/DeliverymenList.java @@ -0,0 +1,106 @@ +package seedu.address.model.deliveryman; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; + +/** + * Wraps all data into a deliverymen list. + * Duplicates are not allowed. + */ +public class DeliverymenList { + private final UniqueDeliverymenList deliverymenList; + + { + deliverymenList = new UniqueDeliverymenList(); + } + + public DeliverymenList() { + } + + public DeliverymenList(DeliverymenList toBeCopied) { + this(); + resetData(toBeCopied); + } + + /// list overwrite operations + + /** + * Replaces the contents of the deliverymen list with {@code deliverymen}. + * {@code deliverymen} must not contain duplicate persons. + */ + public void setDeliverymen(List deliverymen) { + this.deliverymenList.setDeliverymen(deliverymen); + } + + /** + * Resets the contents of the deliverymen list with the contents of {@code newData}. + * @param newData The DeliverymenList to get the contents from + */ + public void resetData(DeliverymenList newData) { + requireNonNull(newData); + + setDeliverymen(newData.getDeliverymenList()); + } + + /// deliveryman-level operations + + /** + * Returns true if the {@code DeliverymenList} contains a {@code deliveryman} + * @param deliveryman + */ + public boolean hasDeliveryman(Deliveryman deliveryman) { + requireNonNull(deliveryman); + return deliverymenList.contains(deliveryman); + } + + /** + * Adds a deliveryman to the {@code deliverymenList} + * @param d + */ + public void addDeliveryman(Deliveryman d) { + if (d.getTag() == null) { + d.assignTag(); + } + deliverymenList.add(d); + } + + /** + * Replaces the {@code target} deliveryman with an {@code edited} deliveryman. + */ + public void updateDeliveryman(Deliveryman target, Deliveryman editedD) { + requireNonNull(editedD); + + if (editedD.getTag() == null) { + editedD.assignTag(); + } + deliverymenList.setDeliveryman(target, editedD); + } + + public void removeDeliveryman(Deliveryman key) { + deliverymenList.remove(key); + } + + @Override + public String toString() { + return deliverymenList.asUnmodifiableObservableList().size() + " deliverymen"; + } + + public ObservableList getDeliverymenList() { + return deliverymenList.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof DeliverymenList + && deliverymenList.equals(((DeliverymenList) other).deliverymenList)); + } + + @Override + public int hashCode() { + return deliverymenList.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/deliveryman/UniqueDeliverymenList.java b/src/main/java/seedu/address/model/deliveryman/UniqueDeliverymenList.java new file mode 100644 index 000000000000..657fefb75efe --- /dev/null +++ b/src/main/java/seedu/address/model/deliveryman/UniqueDeliverymenList.java @@ -0,0 +1,122 @@ +package seedu.address.model.deliveryman; + +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.common.exceptions.DuplicatePersonException; +import seedu.address.model.common.exceptions.PersonNotFoundException; + +/** + * A list of unique deliverymen + */ +public class UniqueDeliverymenList implements Iterable { + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent common as the given argument. + */ + public boolean contains(Deliveryman toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameDeliveryman); + } + + /** + * Adds deliveryman to the list. + * The deliveryman must not already exist in the list. + */ + public void add(Deliveryman toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + //TODO: add appropriate exception for this + throw new DuplicatePersonException(); + } + internalList.add(toAdd); + } + + /** + * Remove the equivalent deliveryman from the list. + * deliveryman must exist in the list. + */ + public void remove(Deliveryman toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new PersonNotFoundException(); + } + } + + /** + * Replaces the deliveryman {@code target} in the list with {@code edited}. + * {@code target} must exist in the list. + * The deliveryman identity of {@code edited} must not be the same as another deliveryman in the list. + */ + public void setDeliveryman(Deliveryman target, Deliveryman edited) { + requireAllNonNull(target, edited); + + int index = internalList.indexOf(target); + if (index == -1) { + //TODO: add appropriate exception + throw new PersonNotFoundException(); + } + + if (!target.isSameDeliveryman(edited) && contains(edited)) { + throw new DuplicatePersonException(); + } + + internalList.set(index, edited); + } + + public void setDeliverymen(UniqueDeliverymenList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + public void setDeliverymen(List deliverymen) { + requireAllNonNull(deliverymen); + + if (!deliverymenAreUnique(deliverymen)) { + throw new DuplicatePersonException(); + } + + internalList.setAll(deliverymen); + } + + public ObservableList asUnmodifiableObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof UniqueDeliverymenList + && internalList.equals(((UniqueDeliverymenList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if all the deliverymen in the list are unique. + */ + private boolean deliverymenAreUnique(List deliverymen) { + for (int i = 0; i < deliverymen.size() - 1; i++) { + for (int j = i + 1; j < deliverymen.size(); j++) { + if (deliverymen.get(i).isSameDeliveryman(deliverymen.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/deliveryman/VersionedDeliverymenList.java b/src/main/java/seedu/address/model/deliveryman/VersionedDeliverymenList.java new file mode 100644 index 000000000000..700609506538 --- /dev/null +++ b/src/main/java/seedu/address/model/deliveryman/VersionedDeliverymenList.java @@ -0,0 +1,100 @@ +package seedu.address.model.deliveryman; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a versioned version of the deliverymen list. + */ +public class VersionedDeliverymenList extends DeliverymenList { + + private final List deliverymenListStateList; + private int currentStatePointer; + + public VersionedDeliverymenList(DeliverymenList initialState) { + super(initialState); + + deliverymenListStateList = new ArrayList<>(); + deliverymenListStateList.add(new DeliverymenList(initialState)); + currentStatePointer = 0; + } + + /** + * Saves a copy of the current {@code DeliverymenList} state at the end of the state list. + * Undone states are removed from the state list. + */ + public void commit() { + removeStatesAfterCurrentPointer(); + deliverymenListStateList.add(new DeliverymenList(this)); + currentStatePointer++; + } + + private void removeStatesAfterCurrentPointer() { + deliverymenListStateList.subList(currentStatePointer + 1, deliverymenListStateList.size()).clear(); + } + + /** + * Restores the deliverymen list to its previous state. + */ + public void undo() { + if (!canUndo()) { + throw new NoUndoableStateException(); + } + currentStatePointer--; + resetData(deliverymenListStateList.get(currentStatePointer)); + } + + /** + * Restores the deliverymen list to its previously undone state. + */ + public void redo() { + if (!canRedo()) { + throw new NoRedoableStateException(); + } + currentStatePointer++; + resetData(deliverymenListStateList.get(currentStatePointer)); + } + + public boolean canUndo() { + return currentStatePointer > 0; + } + + public boolean canRedo() { + return currentStatePointer < deliverymenListStateList.size() - 1; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof VersionedDeliverymenList)) { + return false; + } + + VersionedDeliverymenList otherList = (VersionedDeliverymenList) other; + + return super.equals(otherList) + && deliverymenListStateList.equals(otherList.deliverymenListStateList) + && currentStatePointer == otherList.currentStatePointer; + } + + /** + * Thrown when trying to {@code undo()} but can't. + */ + public static class NoUndoableStateException extends RuntimeException { + private NoUndoableStateException() { + super("Current state pointer at start of deliverymenListState list, unable to undo."); + } + } + + /** + * Thrown when trying to {@code redo()} but can't. + */ + public static class NoRedoableStateException extends RuntimeException { + private NoRedoableStateException() { + super("Current state pointer at end of addressBookState list, unable to redo."); + } + } +} diff --git a/src/main/java/seedu/address/model/deliveryman/exceptions/OrdersLimitExceededException.java b/src/main/java/seedu/address/model/deliveryman/exceptions/OrdersLimitExceededException.java new file mode 100644 index 000000000000..b8638e8a7adb --- /dev/null +++ b/src/main/java/seedu/address/model/deliveryman/exceptions/OrdersLimitExceededException.java @@ -0,0 +1,10 @@ +package seedu.address.model.deliveryman.exceptions; + +/** + * Indicated that the operation exceeds the order limit in deliveryman. + */ +public class OrdersLimitExceededException extends Exception { + public OrdersLimitExceededException() { + super("Operations would result in more orders than limit."); + } +} diff --git a/src/main/java/seedu/address/model/order/Food.java b/src/main/java/seedu/address/model/order/Food.java new file mode 100644 index 000000000000..39fa5ebcd342 --- /dev/null +++ b/src/main/java/seedu/address/model/order/Food.java @@ -0,0 +1,59 @@ +package seedu.address.model.order; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Food's name in the order book. + * Guarantees: immutable; is valid as declared in {@link #isValidFoodName(String)} + */ +public class Food { + + public static final String MESSAGE_FOOD_CONSTRAINTS = + "Food should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the food must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String FOOD_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String foodName; + + /** + * Constructs a {@code Food}. + * + * @param name A valid name. + */ + public Food(String name) { + requireNonNull(name); + checkArgument(isValidFood(name), MESSAGE_FOOD_CONSTRAINTS); + foodName = name; + } + + /** + * Returns true if a given string is a valid Food. + */ + public static boolean isValidFood(String test) { + return test.matches(FOOD_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return foodName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Food // instanceof handles nulls + && foodName.equals(((Food) other).foodName)); // state check + } + + @Override + public int hashCode() { + return foodName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/order/NameContainsKeywordsPredicate.java similarity index 78% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/order/NameContainsKeywordsPredicate.java index c9b5868427ca..461e815732a6 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/order/NameContainsKeywordsPredicate.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.order; 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 Order}'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(Order order) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(order.getName().fullName, keyword)); } @Override diff --git a/src/main/java/seedu/address/model/order/Order.java b/src/main/java/seedu/address/model/order/Order.java new file mode 100644 index 000000000000..0695261f2047 --- /dev/null +++ b/src/main/java/seedu/address/model/order/Order.java @@ -0,0 +1,198 @@ +package seedu.address.model.order; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +import seedu.address.model.TaggedObject; +import seedu.address.model.common.Address; +import seedu.address.model.common.Name; +import seedu.address.model.common.Phone; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.deliveryman.exceptions.OrdersLimitExceededException; + +/** + * Represents an Order in the order book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Order extends TaggedObject { + + // Identity fields + private final Name name; + private final Phone phone; + private final Address address; + private final OrderDate orderDate; + private final Set food = new HashSet<>(); + private OrderStatus orderStatus; + private Deliveryman deliveryman; + + /** + * Every field must be present and not null. + */ + public Order(Name name, Phone phone, Address address, OrderDate orderDate, + Set food) { + this(null, name, phone, address, orderDate, new OrderStatus(), food, null); + } + + /** + * Every field must be present and not null. + */ + public Order(Name name, Phone phone, Address address, OrderDate orderDate, OrderStatus orderStatus, + Set food) { + this(null, name, phone, address, orderDate, orderStatus, food, null); + } + + /** + * Every field must be present and not null besides deliveryman. + */ + public Order(Name name, Phone phone, Address address, OrderDate orderDate, OrderStatus orderStatus, Set food, + Deliveryman deliveryman) { + this(null, name, phone, address, orderDate, orderStatus, food, deliveryman); + } + + /** + * This constructor is used to create an {@code order} with a specified id. + */ + public Order(UUID id, Name name, Phone phone, Address address, OrderDate orderDate, OrderStatus orderStatus, + Set food, Deliveryman deliveryman) { + super(id); + requireAllNonNull(name, phone, address, orderDate, food); + this.name = name; + this.phone = phone; + this.address = address; + this.food.addAll(food); + this.orderDate = orderDate; + this.orderStatus = orderStatus; + this.deliveryman = deliveryman; + } + + /** + * This constructor is used to create a new copy of {@code order}. + */ + public Order(Order order) { + this(null, order.name, order.phone, order.address, order.orderDate, order.orderStatus, order.food, + order.deliveryman); + } + + public Name getName() { + return name; + } + + public OrderDate getDate() { + return this.orderDate; + } + + public Phone getPhone() { + return phone; + } + + public Address getAddress() { + return address; + } + + public OrderStatus getOrderStatus() { + return orderStatus; + } + + public Deliveryman getDeliveryman() { + return deliveryman; + } + + public void setStatusCompleted() { + orderStatus = new OrderStatus("COMPLETED"); + } + /** + * Returns a food set + */ + public Set getFood() { + return food; + } + + public void setDeliveryman(Deliveryman newDeliveryman) throws OrdersLimitExceededException { + assert(!isAlreadyAssignedDeliveryman()); + deliveryman = newDeliveryman; + updateStatusOngoing(); + newDeliveryman.addOrder(this); + } + + private void updateStatusOngoing() { + orderStatus = new OrderStatus("ONGOING"); + } + + public boolean isAlreadyAssignedDeliveryman() { + return deliveryman != null; + } + + public boolean isCompleted() { + return orderStatus.isCompletedStatus(); + } + + public boolean isOngoing() { + return orderStatus.isOngoingStatus(); + } + + /** + * Returns true if both orders of the same name have at least one other identity field that is the same. + * This defines a weaker notion of equality between two orders. + */ + public boolean isSameOrder(Order otherOrder) { + if (otherOrder == this) { + return true; + } + + return otherOrder != null + && otherOrder.getName().equals(getName()) + && (otherOrder.getPhone().equals(getPhone())) + && (otherOrder.getDate().equals(getDate())); + } + + /** + * Returns true if both orders have the same identity and data fields. + * This defines a stronger notion of equality between two orders. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Order)) { + return false; + } + + Order otherOrder = (Order) other; + return otherOrder.getName().equals(getName()) + && otherOrder.getPhone().equals(getPhone()) + && otherOrder.getAddress().equals(getAddress()) + && (otherOrder.getDate().equals(getDate())) + && otherOrder.getFood().equals(getFood()) + && otherOrder.getOrderStatus().equals(getOrderStatus()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, phone, address, orderStatus, food); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Phone: ") + .append(getPhone()) + .append(" Address: ") + .append(getAddress()) + .append(" Date: ") + .append(getDate()) + .append(" Status: ") + .append(getOrderStatus()) + .append(" Food: "); + getFood().forEach(builder::append); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/order/OrderAddressContainsKeywordPredicate.java b/src/main/java/seedu/address/model/order/OrderAddressContainsKeywordPredicate.java new file mode 100644 index 000000000000..d5876d6b9fdc --- /dev/null +++ b/src/main/java/seedu/address/model/order/OrderAddressContainsKeywordPredicate.java @@ -0,0 +1,27 @@ +package seedu.address.model.order; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Order}'s {@code Address} matches any of the keywords given. + */ +public class OrderAddressContainsKeywordPredicate implements Predicate { + private final String keyword; + + public OrderAddressContainsKeywordPredicate(String address) { + keyword = address.trim().toLowerCase(); + } + + @Override + public boolean test(Order order) { + return order.getAddress().value.toLowerCase().contains(keyword); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderAddressContainsKeywordPredicate // instanceof handles nulls + && keyword.equals(((OrderAddressContainsKeywordPredicate) other).keyword)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/order/OrderDate.java b/src/main/java/seedu/address/model/order/OrderDate.java new file mode 100644 index 000000000000..7c7f2073b112 --- /dev/null +++ b/src/main/java/seedu/address/model/order/OrderDate.java @@ -0,0 +1,83 @@ +package seedu.address.model.order; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Represents an Order's date in the order book. + * Guarantees: immutable; is valid as declared in {@link #isValidDate(String)} (String)} + */ +public class OrderDate { + + public static final String MESSAGE_DATE_CONSTRAINTS = + "Date should be in the format dd-MM-yyyy HH:mm:ss and it should be a valid date."; + + private static final SimpleDateFormat sf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); + + private Date date = null; + + /** + * Constructs a {@code OrderDate}. + * + * @param orderDate A valid Order Date. + */ + public OrderDate(String orderDate) { + + requireNonNull(orderDate); + + try { + sf.setLenient(false); + date = sf.parse(orderDate); + } catch (ParseException pE) { + checkArgument(false, MESSAGE_DATE_CONSTRAINTS); + } + } + + /** + * Returns true if a given string is a valid date. + */ + public static boolean isValidDate(String orderDate) { + try { + sf.setLenient(false); + sf.parse(orderDate); + return true; + } catch (ParseException pE) { + return false; + } + } + + public Date getDate() { + return date; + } + + public Date getShortenedDate() { + try { + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM"); + return dateFormat.parse(dateFormat.format(date)); + } catch (ParseException pE) { + return date; + } + } + + @Override + public String toString() { + return sf.format(date); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderDate // instanceof handles nulls + && date.equals(((OrderDate) other).date)); // state check + } + + @Override + public int hashCode() { + return date.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/order/OrderDatePredicate.java b/src/main/java/seedu/address/model/order/OrderDatePredicate.java new file mode 100644 index 000000000000..5bd58587e525 --- /dev/null +++ b/src/main/java/seedu/address/model/order/OrderDatePredicate.java @@ -0,0 +1,42 @@ +package seedu.address.model.order; + +import java.util.Date; +import java.util.List; +import java.util.function.Predicate; + +/** + * Tests that a {@code Order}'s {@code Date} matches any of the keywords given. + */ +public class OrderDatePredicate implements Predicate { + public static final int VALID_SEARCH_DATE_RANGE_SIZE = 2; + public static final int LOWER_DATE_RANGE_INDEX = 0; + public static final int UPPER_DATE_RANGE_INDEX = 1; + + private Predicate datePredicate; + + public OrderDatePredicate(List dates) { + assert dates.size() < 3; + + if (dates.size() == VALID_SEARCH_DATE_RANGE_SIZE) { + Date lowerDateBoundary = dates.get(LOWER_DATE_RANGE_INDEX); + Date upperDateBoundary = dates.get(UPPER_DATE_RANGE_INDEX); + assert (lowerDateBoundary.before(upperDateBoundary) && lowerDateBoundary != upperDateBoundary); + datePredicate = new OrderDateRangePredicate(lowerDateBoundary, upperDateBoundary); + } else { + // One date in list + datePredicate = new OrderDateSinglePredicate(dates.get(0)); + } + } + + @Override + public boolean test(Order order) { + return datePredicate.test(order); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderDatePredicate // instanceof handles nulls + && datePredicate.equals(((OrderDatePredicate) other).datePredicate)); // state check + } +} diff --git a/src/main/java/seedu/address/model/order/OrderDateRangePredicate.java b/src/main/java/seedu/address/model/order/OrderDateRangePredicate.java new file mode 100644 index 000000000000..363127162aaa --- /dev/null +++ b/src/main/java/seedu/address/model/order/OrderDateRangePredicate.java @@ -0,0 +1,35 @@ +package seedu.address.model.order; + +import java.util.Date; +import java.util.function.Predicate; + +/** + * Tests that a {@code Order}'s {@code Date} is within the 2 dates given. + */ +public class OrderDateRangePredicate implements Predicate { + private final Date lowerDateBoundary; + private final Date upperDateBoundary; + + public OrderDateRangePredicate(Date lowerDateBoundary, Date upperDateBoundary) { + this.lowerDateBoundary = lowerDateBoundary; + this.upperDateBoundary = upperDateBoundary; + } + + @Override + public boolean test(Order order) { + return isWithinDateBoundary(order.getDate().getDate()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderDateRangePredicate // instanceof handles nulls + && lowerDateBoundary.equals(((OrderDateRangePredicate) other).lowerDateBoundary) + && upperDateBoundary.equals(((OrderDateRangePredicate) other).upperDateBoundary)); // state check + } + + private boolean isWithinDateBoundary(Date date) { + return (date.equals(lowerDateBoundary) || date.equals(upperDateBoundary) + || (date.after(lowerDateBoundary) && date.before(upperDateBoundary))); + } +} diff --git a/src/main/java/seedu/address/model/order/OrderDateSinglePredicate.java b/src/main/java/seedu/address/model/order/OrderDateSinglePredicate.java new file mode 100644 index 000000000000..5c5e4148e004 --- /dev/null +++ b/src/main/java/seedu/address/model/order/OrderDateSinglePredicate.java @@ -0,0 +1,28 @@ +package seedu.address.model.order; + +import java.util.Date; +import java.util.function.Predicate; + + +/** + * Tests that a {@code Order}'s {@code Date} matches any of the keywords given. + */ +public class OrderDateSinglePredicate implements Predicate { + private final Date keyword; + + public OrderDateSinglePredicate(Date date) { + keyword = date; + } + + @Override + public boolean test(Order order) { + return order.getDate().getDate().equals(keyword); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderDateSinglePredicate // instanceof handles nulls + && keyword.equals(((OrderDateSinglePredicate) other).keyword)); // state check + } +} diff --git a/src/main/java/seedu/address/model/order/OrderFoodContainsKeywordPredicate.java b/src/main/java/seedu/address/model/order/OrderFoodContainsKeywordPredicate.java new file mode 100644 index 000000000000..4362ff587aa3 --- /dev/null +++ b/src/main/java/seedu/address/model/order/OrderFoodContainsKeywordPredicate.java @@ -0,0 +1,27 @@ +package seedu.address.model.order; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Order}'s {@code Food} matches any of the keywords given. + */ +public class OrderFoodContainsKeywordPredicate implements Predicate { + private final String keyword; + + public OrderFoodContainsKeywordPredicate(String food) { + keyword = food.trim().toLowerCase(); + } + + @Override + public boolean test(Order order) { + return order.getFood().stream() + .anyMatch(orderFood -> orderFood.foodName.toLowerCase().contains(keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderFoodContainsKeywordPredicate // instanceof handles nulls + && keyword.equals(((OrderFoodContainsKeywordPredicate) other).keyword)); // state check + } +} diff --git a/src/main/java/seedu/address/model/order/OrderNameContainsKeywordPredicate.java b/src/main/java/seedu/address/model/order/OrderNameContainsKeywordPredicate.java new file mode 100644 index 000000000000..c3aaff6a862a --- /dev/null +++ b/src/main/java/seedu/address/model/order/OrderNameContainsKeywordPredicate.java @@ -0,0 +1,26 @@ +package seedu.address.model.order; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Order}'s {@code Name} matches any of the keywords given. + */ +public class OrderNameContainsKeywordPredicate implements Predicate { + private final String keyword; + + public OrderNameContainsKeywordPredicate(String name) { + keyword = name.trim().toLowerCase(); + } + + @Override + public boolean test(Order order) { + return order.getName().fullName.toLowerCase().contains(keyword); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderNameContainsKeywordPredicate // instanceof handles nulls + && keyword.equals(((OrderNameContainsKeywordPredicate) other).keyword)); // state check + } +} diff --git a/src/main/java/seedu/address/model/order/OrderPhoneContainsKeywordPredicate.java b/src/main/java/seedu/address/model/order/OrderPhoneContainsKeywordPredicate.java new file mode 100644 index 000000000000..4e0d8c45f3ca --- /dev/null +++ b/src/main/java/seedu/address/model/order/OrderPhoneContainsKeywordPredicate.java @@ -0,0 +1,29 @@ +package seedu.address.model.order; + +import java.util.List; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Order}'s {@code Phone} matches any of the keywords given. + */ +public class OrderPhoneContainsKeywordPredicate implements Predicate { + private final List keywords; + + public OrderPhoneContainsKeywordPredicate(List phone) { + keywords = phone; + } + + @Override + public boolean test(Order order) { + return keywords.stream() + .anyMatch(keyword -> order.getPhone().value.contains(keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderPhoneContainsKeywordPredicate // instanceof handles nulls + && keywords.equals(((OrderPhoneContainsKeywordPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/order/OrderStatus.java b/src/main/java/seedu/address/model/order/OrderStatus.java new file mode 100644 index 000000000000..ddf0ddb529be --- /dev/null +++ b/src/main/java/seedu/address/model/order/OrderStatus.java @@ -0,0 +1,85 @@ +package seedu.address.model.order; + +/** + * Represents an Order's Status in the order book. + * Guarantees: immutable; is valid as declared in {@link #isValidDate(String)} (String)} + */ +public class OrderStatus { + + public static final String MESSAGE_STATUS_CONSTRAINTS = + "Status should be either: PENDING, ONGOING or COMPLETED"; + + /** + * Valid Status Fields. + */ + private enum Status { + PENDING, + ONGOING, + COMPLETED + } + + private Status orderState = null; + + /** + * Constructs a {@code OrderStatus}. + */ + public OrderStatus() { + this(Status.PENDING.name()); + } + + /** + * Constructs a {@code OrderStatus} with a parameter. + * + * @param status A valid Order Status string + */ + public OrderStatus(String status) { + orderState = Status.valueOf(status); + } + + /** + * Checks if the status is ongoing. + */ + public boolean isOngoingStatus() { + return orderState.equals(Status.ONGOING); + } + + /** + * Checks if the status is completed. + */ + public boolean isCompletedStatus() { + return orderState.equals(Status.COMPLETED); + } + + /** + * Returns true if a given string is a valid status. + */ + public static boolean isValidStatus(String orderStatus) { + + for (Status s : Status.values()) { + if (orderStatus.equals(s.name())) { + return true; + } + } + + return false; + } + + + @Override + public String toString() { + return orderState.toString(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderStatus // instanceof handles nulls + && orderState.equals(((OrderStatus) other).orderState)); // state check + } + + @Override + public int hashCode() { + return orderState.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/order/OrderStatusContainsKeywordPredicate.java b/src/main/java/seedu/address/model/order/OrderStatusContainsKeywordPredicate.java new file mode 100644 index 000000000000..ad5aeba2ab67 --- /dev/null +++ b/src/main/java/seedu/address/model/order/OrderStatusContainsKeywordPredicate.java @@ -0,0 +1,28 @@ +package seedu.address.model.order; + +import java.util.List; +import java.util.function.Predicate; + +/** + * Tests that a {@code Order}'s {@code Status} matches the keywords given. + */ +public class OrderStatusContainsKeywordPredicate implements Predicate { + private final List keywords; + + public OrderStatusContainsKeywordPredicate(List statuses) { + keywords = statuses; + } + + @Override + public boolean test(Order order) { + return keywords.stream() + .anyMatch(keyword -> order.getOrderStatus().equals(keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderStatusContainsKeywordPredicate // instanceof handles nulls + && keywords.equals(((OrderStatusContainsKeywordPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/order/UniqueOrderList.java b/src/main/java/seedu/address/model/order/UniqueOrderList.java new file mode 100644 index 000000000000..4e9ac7bdca89 --- /dev/null +++ b/src/main/java/seedu/address/model/order/UniqueOrderList.java @@ -0,0 +1,135 @@ +package seedu.address.model.order; + +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.order.exceptions.DuplicateOrderException; +import seedu.address.model.order.exceptions.OrderNotFoundException; + +/** + * A list of orders that enforces uniqueness between its elements and does not allow nulls. + * An order is considered unique by comparing using {@code Order#isSameOrder(Order)}. As such, adding and updating of + * orders uses Order#isSameOrder(Order) for equality so as to ensure that the Order being added or updated is + * unique in terms of identity in the UniqueOrderList. However, the removal of an Order uses Order#equals(Object) so + * as to ensure that the order with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Order#isSameOrder(Order) + */ +public class UniqueOrderList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent order as the given argument. + */ + public boolean contains(Order toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameOrder); + } + + /** + * Adds an Order to the list. + * The order must not already exist in the list. + */ + public void add(Order toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateOrderException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the order {@code target} in the list with {@code editedOrder}. + * {@code target} must exist in the list. + * The order identity of {@code editedOrder} must not be the same as another existing order in the list. + */ + public void setOrder(Order target, Order editedOrder) { + requireAllNonNull(target, editedOrder); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new OrderNotFoundException(); + } + + if (!target.isSameOrder(editedOrder) && contains(editedOrder)) { + throw new DuplicateOrderException(); + } + + internalList.set(index, editedOrder); + } + + public void setOrder(UniqueOrderList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code orders}. + * {@code orders} must not contain duplicate persons. + */ + public void setOrder(List orders) { + requireAllNonNull(orders); + if (!ordersAreUnique(orders)) { + throw new DuplicateOrderException(); + } + + internalList.setAll(orders); + } + + /** + * Removes the equivalent order from the list. + * The order must exist in the list. + */ + public void remove(Order toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new OrderNotFoundException(); + } + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueOrderList // instanceof handles nulls + && internalList.equals(((UniqueOrderList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code orders} contains only unique orders. + */ + private boolean ordersAreUnique(List orders) { + for (int i = 0; i < orders.size() - 1; i++) { + for (int j = i + 1; j < orders.size(); j++) { + if (orders.get(i).isSameOrder(orders.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/order/exceptions/DuplicateOrderException.java b/src/main/java/seedu/address/model/order/exceptions/DuplicateOrderException.java new file mode 100644 index 000000000000..d6b2597d7ed3 --- /dev/null +++ b/src/main/java/seedu/address/model/order/exceptions/DuplicateOrderException.java @@ -0,0 +1,11 @@ +package seedu.address.model.order.exceptions; + +/** + * Signals that the operation will result in duplicate Order (Orders are considered duplicates if they have the same + * identity). + */ +public class DuplicateOrderException extends RuntimeException { + public DuplicateOrderException() { + super("Operation would result in duplicate orders"); + } +} diff --git a/src/main/java/seedu/address/model/order/exceptions/OrderNotFoundException.java b/src/main/java/seedu/address/model/order/exceptions/OrderNotFoundException.java new file mode 100644 index 000000000000..b3091c7208ce --- /dev/null +++ b/src/main/java/seedu/address/model/order/exceptions/OrderNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.address.model.order.exceptions; + +/** + * Signals that the operation is unable to find the specified order. + */ +public class OrderNotFoundException extends RuntimeException { +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index 38a7629e9a2d..000000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; - public static final String MESSAGE_EMAIL_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n" - + "2. This is followed by a '@' and then a domain name. " - + "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."; - // 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 - private static final String DOMAIN_MIDDLE_REGEX = "[a-zA-Z0-9.-]*"; // alphanumeric, period and hyphen - private static final String DOMAIN_LAST_CHARACTER_REGEX = "[^\\W_]$"; - public static final String EMAIL_VALIDATION_REGEX = LOCAL_PART_REGEX + "@" - + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_EMAIL_CONSTRAINTS); - value = email; - } - - /** - * Returns if a given string is a valid email. - */ - public static boolean isValidEmail(String test) { - return test.matches(EMAIL_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 557a7a60cd51..000000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * 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) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - 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()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - -} 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 5856aa42e6b5..000000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,135 +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(); - - /** - * 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 FXCollections.unmodifiableObservableList(internalList); - } - - @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 fa764426ca73..000000000000 --- 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/user/UniqueUserList.java b/src/main/java/seedu/address/model/user/UniqueUserList.java new file mode 100644 index 000000000000..4e0d812433cf --- /dev/null +++ b/src/main/java/seedu/address/model/user/UniqueUserList.java @@ -0,0 +1,135 @@ +package seedu.address.model.user; + +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.common.exceptions.DuplicatePersonException; +import seedu.address.model.common.exceptions.PersonNotFoundException; + +/** + * A list of unique users + */ +public class UniqueUserList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent user as the given argument. + */ + public boolean contains(User toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameUser); + } + + /** + * Returns true if the list contains an equivalent user as the given username and password. + */ + public boolean check(User toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameExistingUser); + } + + /** + * Adds a user to the list. + * The user must not already exist in the list. + */ + public void add(User toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePersonException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the user {@code target} in the list with {@code editedPerson}. + * {@code target} must exist in the list. + * The common identity of {@code editedPerson} must not be the same as another existing user in the list. + */ + public void setUser(User target, User editedPerson) { + requireAllNonNull(target, editedPerson); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + if (!target.isSameUser(editedPerson) && contains(editedPerson)) { + throw new DuplicatePersonException(); + } + + internalList.set(index, editedPerson); + } + + /** + * Removes the equivalent user from the list. + * The user must exist in the list. + */ + public void remove(User toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new PersonNotFoundException(); + } + } + + public void setUsers(UniqueUserList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code users}. + * {@code users} must not contain duplicate users. + */ + public void setUsers(List users) { + requireAllNonNull(users); + if (!usersAreUnique(users)) { + throw new DuplicatePersonException(); + } + + internalList.setAll(users); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueUserList // instanceof handles nulls + && internalList.equals(((UniqueUserList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code users} contains only unique users. + */ + private boolean usersAreUnique(List users) { + for (int i = 0; i < users.size() - 1; i++) { + for (int j = i + 1; j < users.size(); j++) { + if (users.get(i).isSameUser(users.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/user/User.java b/src/main/java/seedu/address/model/user/User.java new file mode 100644 index 000000000000..d4fdd5563dee --- /dev/null +++ b/src/main/java/seedu/address/model/user/User.java @@ -0,0 +1,118 @@ +package seedu.address.model.user; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import seedu.address.model.common.Name; +import seedu.address.model.common.Password; +import seedu.address.model.common.Username; + +/** + * Represents a User in the FoodZoom. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class User { + + // Identity fields + private Name name; + private final Username username; + private final Password password; + + /** + * Every field must be present and not null. + */ + public User(Name name, Username username, Password password) { + requireAllNonNull(name, username, password); + this.name = name; + this.username = username; + this.password = password; + } + + public User(Username username, Password password) { + requireAllNonNull(username, password); + this.username = username; + this.password = password; + } + + public Name getName() { + return name; + } + + public Username getUsername() { + return username; + } + + public Password getPassword() { + return password; + } + + /** + * Returns true if both users 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 isSameUser(User otherUser) { + if (otherUser == this) { + return true; + } + + return otherUser != null + && otherUser.getUsername().equals(getUsername()); + } + + /** + * Returns true if both users of the same username and password. + */ + public boolean isSameExistingUser(User otherUser) { + if (otherUser == this) { + return true; + } + + return otherUser != null + && otherUser.getUsername().equals(getUsername()) + && otherUser.getPassword().equals(getPassword()); + } + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof User)) { + return false; + } + + User otherPerson = (User) other; + if (otherPerson.getName() == null) { + return otherPerson.getUsername().equals(getUsername()) + && otherPerson.getPassword().equals(getPassword()); + } else { + return otherPerson.getName().equals(getName()) + && otherPerson.getUsername().equals(getUsername()) + && otherPerson.getPassword().equals(getPassword()); + } + + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + + //To print for Login + if (getName() == null) { + builder.append("Username: ") + .append(getUsername()); + } else { + builder.append(getName()) + .append(" Username: ") + .append(getUsername()) + .append(" Password: ") + .append(getPassword()); + } + + + return builder.toString(); + } + + +} diff --git a/src/main/java/seedu/address/model/user/UserSession.java b/src/main/java/seedu/address/model/user/UserSession.java new file mode 100644 index 000000000000..2a358c69441e --- /dev/null +++ b/src/main/java/seedu/address/model/user/UserSession.java @@ -0,0 +1,61 @@ +package seedu.address.model.user; + +import static java.util.Objects.requireNonNull; + +/** + * UserSession represent a logged in user session. + */ +public class UserSession { + + private User loggedInUser; + private boolean isLoggedIn; + + /** + * Initializes an empty UserSession. + */ + public UserSession() { + isLoggedIn = false; + } + + /** + * Initializes a UserSession with user. + * @param toLogin User that has successfully logged in. + */ + public UserSession(User toLogin) { + requireNonNull(toLogin); + loggedInUser = toLogin; + this.isLoggedIn = true; + } + + /** + * Set up user session. + * @param toLogin User that has successfully logged in. + */ + public void setUserSession(User toLogin) { + requireNonNull(toLogin); + this.loggedInUser = toLogin; + this.isLoggedIn = true; + } + + /** + * Check if user is already logged into FoodZoom. + */ + public boolean isUserAlreadyLoggedIn() { + return this.isLoggedIn; + } + + /** + * To clear user session details. + */ + public void clearUserSession() { + this.isLoggedIn = false; + this.loggedInUser = null; + } + + /** + * Return logged in user details. + */ + public User getLoggedInUserDetails() { + return this.loggedInUser; + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facfa..1771a4e9a7ea 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -4,57 +4,100 @@ 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.tag.Tag; +import seedu.address.model.OrderBook; +import seedu.address.model.ReadOnlyOrderBook; +import seedu.address.model.ReadOnlyUsersList; +import seedu.address.model.UsersList; +import seedu.address.model.common.Address; +import seedu.address.model.common.Name; +import seedu.address.model.common.Password; +import seedu.address.model.common.Phone; +import seedu.address.model.common.Username; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.deliveryman.DeliverymenList; +import seedu.address.model.order.Food; +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderDate; +import seedu.address.model.user.User; /** - * Contains utility methods for populating {@code AddressBook} with sample data. + * Contains utility methods for populating {@code OrderBook} 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 Order[] getSampleOrders() { + Order[] orders = new Order[]{ + new Order(new Name("Alex Yeoh"), new Phone("87438807"), + new Address("Blk 30 Geylang Street 29, #06-40, 388670"), new OrderDate("20-10-2018 10:00:00"), + getFoodSet("Ice Tea")), + new Order(new Name("Bernice Yu"), new Phone("99272758"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18, 192355"), + new OrderDate("21-10-2018 10:00:00"), getFoodSet("Chicken Rice")), + new Order(new Name("Charlotte Oliveiro"), new Phone("93210283"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04, 564322"), + new OrderDate("21-10-2018 14:00:00"), getFoodSet("Nasi Goreng")), + new Order(new Name("David Li"), new Phone("91031282"), + new Address("Blk 436 Jurong West Street 26, #16-43, 612234"), + new OrderDate("24-10-2018 10:00:00"), + getFoodSet("Satay")), + new Order(new Name("Irfan Ibrahim"), new Phone("92492021"), + new Address("Blk 47 Tampines Street 20, #17-35, 512234"), new OrderDate("26-10-2018 10:00:00"), + getFoodSet("Fish and Chips")), + new Order(new Name("Roy Balakrishnan"), new Phone("92624417"), + new Address("Blk 45 Aljunied Street 85, #11-31, 380095"), new OrderDate("28-10-2018 10:00:00"), + getFoodSet("Sugercane Juice")) }; + return orders; } - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + + public static Deliveryman[] getSampleDeliverymen() { + return new Deliveryman[]{ + new Deliveryman(new Name("Hoh Chi Kao")), + new Deliveryman(new Name("Tan Yin Jing")), + new Deliveryman(new Name("Rajul Rahesh")), + new Deliveryman(new Name("Manika Monuela")) + }; + } + + public static ReadOnlyOrderBook getSampleOrderBook() { + OrderBook sampleAb = new OrderBook(); + for (Order sampleOrder : getSampleOrders()) { + sampleAb.addOrder(sampleOrder); } return sampleAb; } + public static User[] getSampleUsers() { + return new User[]{ + new User(new Name("Alice Pauline"), new Username("alicepauline"), new Password("alicepauline01")), + new User(new Name("Benson Meier"), new Username("bensonmeier"), new Password("bensonmeier02")), + new User(new Name("Carl Kurz"), new Username("carlkurz"), new Password("carlkurz03")), + new User(new Name("Hoon Meier"), new Username("hoonmeier"), new Password("hoonmeier04")) + }; + } + + public static ReadOnlyUsersList getSampleUsersList() { + UsersList usersList = new UsersList(); + for (User user : getSampleUsers()) { + usersList.addUser(user); + } + return usersList; + } + + public static DeliverymenList getSampleDeliverymenList() { + DeliverymenList sampleDl = new DeliverymenList(); + for (Deliveryman sampleD : getSampleDeliverymen()) { + sampleDl.addDeliveryman(sampleD); + } + return sampleDl; + } + /** - * Returns a tag set containing the list of strings given. + * Returns a food set containing the list of strings given. */ - public static Set getTagSet(String... strings) { + public static Set getFoodSet(String... strings) { return Arrays.stream(strings) - .map(Tag::new) + .map(Food::new) .collect(Collectors.toSet()); } - } 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 4599182b3f92..000000000000 --- 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/FoodZoomStorage.java b/src/main/java/seedu/address/storage/FoodZoomStorage.java new file mode 100644 index 000000000000..62027b4cc3e1 --- /dev/null +++ b/src/main/java/seedu/address/storage/FoodZoomStorage.java @@ -0,0 +1,28 @@ +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.ReadOnlyOrderBook; +import seedu.address.model.deliveryman.DeliverymenList; + +/** + * Represents storage for FoodZoom + */ +public interface FoodZoomStorage { + Path getFoodZoomFilePath(); + + Optional readOrderBook() throws DataConversionException, IOException; + + Optional readOrderBook(Path filePath) throws DataConversionException, IOException; + + Optional readDeliverymenList() throws DataConversionException, IOException; + + Optional readDeliverymenList(Path filePath) throws DataConversionException, IOException; + + void saveFoodZoom(ReadOnlyOrderBook orderBook, DeliverymenList deliverymenList) throws IOException; + + void saveFoodZoom(ReadOnlyOrderBook orderBook, DeliverymenList deliverymenList, Path filePath) throws IOException; +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java index 2ab927023cc4..38b7f0723f46 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java @@ -31,6 +31,7 @@ public Optional readUserPrefs() throws DataConversionException { /** * Similar to {@link #readUserPrefs()} + * * @param prefsFilePath location of the data. Cannot be null. * @throws DataConversionException if the file format is not as expected. */ diff --git a/src/main/java/seedu/address/storage/OrderBookStorage.java b/src/main/java/seedu/address/storage/OrderBookStorage.java new file mode 100644 index 000000000000..e76468e99a75 --- /dev/null +++ b/src/main/java/seedu/address/storage/OrderBookStorage.java @@ -0,0 +1,48 @@ +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.OrderBook; +import seedu.address.model.ReadOnlyOrderBook; + +/** + * Represents a storage for {@link OrderBook}. + */ +public interface OrderBookStorage { + + /** + * Returns the file path of the data file. + */ + Path getOrderBookFilePath(); + + /** + * Returns OrderBook data as a {@link ReadOnlyOrderBook}. + * 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 readOrderBook() throws DataConversionException, IOException; + + /** + * @see #getAddressBookFilePath() + */ + Optional readOrderBook(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyAddressBook} to the storage. + * + * @param orderBook cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveOrderBook(ReadOnlyOrderBook orderBook) throws IOException; + + /** + * @see #saveOrderBook(ReadOnlyOrderBook) + */ + void saveOrderBook(ReadOnlyOrderBook orderBook, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index 28791127999b..f9b234ba1fb3 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -4,16 +4,20 @@ import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.FoodZoomChangedEvent; +import seedu.address.commons.events.model.UsersListChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyOrderBook; +import seedu.address.model.ReadOnlyUsersList; import seedu.address.model.UserPrefs; +import seedu.address.model.deliveryman.DeliverymenList; +import seedu.address.storage.user.UsersListStorage; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends FoodZoomStorage, UserPrefsStorage, UsersListStorage { @Override Optional readUserPrefs() throws DataConversionException, IOException; @@ -22,18 +26,38 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { void saveUserPrefs(UserPrefs userPrefs) throws IOException; @Override - Path getAddressBookFilePath(); + Path getUsersListFilePath(); @Override - Optional readAddressBook() throws DataConversionException, IOException; + Optional readUsersList() throws DataConversionException, IOException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveUsersList(ReadOnlyUsersList usersList) throws IOException; /** - * Saves the current version of the Address Book to the hard disk. - * Creates the data file if it is missing. + * Saves the current version of the UsersList to the hard disk. + * Creates the data file if it is missing. * Raises {@link DataSavingExceptionEvent} if there was an error during saving. */ - void handleAddressBookChangedEvent(AddressBookChangedEvent abce); + void handleUsersListChangedEvent(UsersListChangedEvent ulce); + + @Override + Optional readOrderBook() throws DataConversionException, IOException; + + @Override + Optional readOrderBook(Path filePath) throws DataConversionException, IOException; + + @Override + Optional readDeliverymenList() throws DataConversionException, IOException; + + @Override + Optional readDeliverymenList(Path filePath) throws DataConversionException, IOException; + + @Override + void saveFoodZoom(ReadOnlyOrderBook orderBook, DeliverymenList deliverymenList) throws IOException; + + @Override + void saveFoodZoom(ReadOnlyOrderBook orderBook, DeliverymenList deliverymenList, Path filePath) throws IOException; + + void handleFoodZoomChangedEvent(FoodZoomChangedEvent fzce); } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index b0df908a76a7..e1a41422695b 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -9,26 +9,72 @@ import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.FoodZoomChangedEvent; +import seedu.address.commons.events.model.UsersListChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyOrderBook; +import seedu.address.model.ReadOnlyUsersList; import seedu.address.model.UserPrefs; +import seedu.address.model.deliveryman.DeliverymenList; +import seedu.address.storage.user.UsersListStorage; /** - * Manages storage of AddressBook data in local storage. + * Manages storage of OrderBook data in local storage. */ public class StorageManager extends ComponentManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; + private FoodZoomStorage foodZoomStorage; private UserPrefsStorage userPrefsStorage; + private UsersListStorage usersListStorage; - - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(UsersListStorage usersListStorage, FoodZoomStorage foodZoomStorage, + UserPrefsStorage userPrefsStorage) { super(); - this.addressBookStorage = addressBookStorage; + this.foodZoomStorage = foodZoomStorage; this.userPrefsStorage = userPrefsStorage; + this.usersListStorage = usersListStorage; + } + + // ================ UsersList methods ============================== + + @Override + public Path getUsersListFilePath() { + return usersListStorage.getUsersListFilePath(); + } + + @Override + public Optional readUsersList() throws DataConversionException, IOException { + return readUsersList(usersListStorage.getUsersListFilePath()); + } + + @Override + public Optional readUsersList(Path filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return usersListStorage.readUsersList(filePath); + } + + @Override + public void saveUsersList(ReadOnlyUsersList usersList) throws IOException { + saveUsersList(usersList, usersListStorage.getUsersListFilePath()); + } + + @Override + public void saveUsersList(ReadOnlyUsersList usersList, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + usersListStorage.saveUsersList(usersList, filePath); + } + + @Override + @Subscribe + public void handleUsersListChangedEvent(UsersListChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); + try { + saveUsersList(event.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } } // ================ UserPrefs methods ============================== @@ -48,46 +94,56 @@ public void saveUserPrefs(UserPrefs userPrefs) throws IOException { userPrefsStorage.saveUserPrefs(userPrefs); } + @Override + public Path getFoodZoomFilePath() { + return foodZoomStorage.getFoodZoomFilePath(); + } - // ================ AddressBook methods ============================== + //================================ data read methods ====================================== + @Override + public Optional readOrderBook() throws DataConversionException, IOException { + return readOrderBook(foodZoomStorage.getFoodZoomFilePath()); + } @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); + public Optional readOrderBook(Path filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return foodZoomStorage.readOrderBook(filePath); } @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); + public Optional readDeliverymenList() throws DataConversionException, IOException { + return readDeliverymenList(foodZoomStorage.getFoodZoomFilePath()); } @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { + public Optional readDeliverymenList(Path filePath) throws DataConversionException, IOException { logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); + return foodZoomStorage.readDeliverymenList(filePath); } + //================================ data save methods ====================================== + @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); + public void saveFoodZoom(ReadOnlyOrderBook orderBook, DeliverymenList deliverymenList) throws IOException { + saveFoodZoom(orderBook, deliverymenList, foodZoomStorage.getFoodZoomFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveFoodZoom(ReadOnlyOrderBook orderBook, DeliverymenList deliverymenList, Path filePath) throws + IOException { logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); + foodZoomStorage.saveFoodZoom(orderBook, deliverymenList, filePath); } - @Override @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { + public void handleFoodZoomChangedEvent(FoodZoomChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); try { - saveAddressBook(event.data); + saveFoodZoom(event.orderBook, event.deliverymenList); } catch (IOException e) { raise(new DataSavingExceptionEvent(e)); } } - } diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/address/storage/UserPrefsStorage.java index 877b0ee5c4f0..9fb18a6be2a6 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/address/storage/UserPrefsStorage.java @@ -19,14 +19,16 @@ public interface UserPrefsStorage { /** * Returns UserPrefs data from storage. - * Returns {@code Optional.empty()} if storage file is not found. + * 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. + * @throws IOException if there was any problem when reading from the storage. */ Optional readUserPrefs() throws DataConversionException, IOException; /** * Saves the given {@link seedu.address.model.UserPrefs} to the storage. + * * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/address/storage/XmlAdaptedFood.java b/src/main/java/seedu/address/storage/XmlAdaptedFood.java new file mode 100644 index 000000000000..3ce8e4d0de6e --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedFood.java @@ -0,0 +1,63 @@ +package seedu.address.storage; + +import javax.xml.bind.annotation.XmlValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.order.Food; + +/** + * JAXB-friendly adapted version of the Food. + */ +public class XmlAdaptedFood { + + @XmlValue + private String foodName; + + /** + * Constructs an XmlAdaptedTag. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedFood() { + } + + /** + * Constructs a {@code XmlAdaptedFood} with the given {@code foodName}. + */ + public XmlAdaptedFood(String foodName) { + this.foodName = foodName; + } + + /** + * Converts a given Food into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedFood(Food source) { + foodName = source.foodName; + } + + /** + * Converts this jaxb-friendly adapted food object into the model's Food object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted order + */ + public Food toModelType() throws IllegalValueException { + if (!Food.isValidFood(foodName)) { + throw new IllegalValueException(Food.MESSAGE_FOOD_CONSTRAINTS); + } + return new Food(foodName); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedFood)) { + return false; + } + + return foodName.equals(((XmlAdaptedFood) other).foodName); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedOrder.java b/src/main/java/seedu/address/storage/XmlAdaptedOrder.java new file mode 100644 index 000000000000..5f8005900b88 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedOrder.java @@ -0,0 +1,211 @@ +package seedu.address.storage; + +import static seedu.address.model.TaggedObject.MESSAGE_INVALID_ID; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlID; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.common.Address; +import seedu.address.model.common.Name; +import seedu.address.model.common.Phone; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.order.Food; +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderDate; +import seedu.address.model.order.OrderStatus; + +/** + * JAXB-friendly version of the Order. + */ +public class XmlAdaptedOrder { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Order's %s field is missing!"; + + @XmlAttribute (required = true) + @XmlID + private String tag; + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private String phone; + @XmlElement(required = true) + private String address; + @XmlElement(required = true) + private String date; + @XmlElement(required = true) + private String status; + @XmlElement(required = true) + private List food = new ArrayList<>(); + @XmlElement + private String deliveryman; + + /** + * Constructs an XmlAdaptedOrder. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedOrder() { + } + + /** + * Constructs an {@code XmlAdaptedOrder} with the given order details. + */ + public XmlAdaptedOrder(String tag, String name, String phone, String address, String date, String status, + List food, String deliveryman) { + this.tag = tag; + this.name = name; + this.phone = phone; + this.address = address; + this.date = date; + this.status = status; + this.deliveryman = deliveryman; + + if (food == null) { + this.food = new ArrayList<>(); + } else { + this.food = new ArrayList<>(food); + } + } + + /** + * Constructs an {@code XmlAdaptedOrder} with the given order details. + */ + public XmlAdaptedOrder(String name, String phone, String address, String date, String status, + List food, String deliveryman) { + this(UUID.randomUUID().toString(), name, phone, address, date, status, food, deliveryman); + } + + /** + * Converts a given Order into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedOrder + */ + public XmlAdaptedOrder(Order source) { + tag = source.getTag().toString(); + name = source.getName().fullName; + phone = source.getPhone().value; + address = source.getAddress().value; + date = source.getDate().toString(); + status = source.getOrderStatus().toString(); + food = source.getFood().stream() + .map(XmlAdaptedFood::new) + .collect(Collectors.toList()); + if (source.getDeliveryman() != null) { + deliveryman = source.getDeliveryman().getName().fullName; + } + } + + /** + * Converts this jaxb-friendly adapted order object into the model's Order object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted order + */ + public Order toModelType() throws IllegalValueException { + final List foodStore = new ArrayList<>(); + for (XmlAdaptedFood foodItem : food) { + foodStore.add(foodItem.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_NAME_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_PHONE_CONSTRAINTS); + } + final Phone modelPhone = new Phone(phone); + + + if (address == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + } + if (!Address.isValidAddress(address)) { + throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + } + final Address modelAddress = new Address(address); + + + if (date == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Date.class.getSimpleName())); + } + if (!OrderDate.isValidDate(date)) { + throw new IllegalValueException(OrderDate.MESSAGE_DATE_CONSTRAINTS); + } + final OrderDate modelDate = new OrderDate(date); + + if (foodStore.isEmpty()) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Food.class.getSimpleName())); + } + final Set modelFood = new HashSet<>(foodStore); + + if (tag == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Tag")); + } + + UUID modelTag; + try { + modelTag = UUID.fromString(tag); + } catch (NumberFormatException e) { + throw new IllegalValueException(MESSAGE_INVALID_ID); + } + + if (status == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + OrderStatus.class.getSimpleName())); + } + if (!OrderStatus.isValidStatus(status)) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + OrderStatus.class.getSimpleName())); + } + final OrderStatus orderStatus = new OrderStatus(status); + + final Deliveryman modelDeliveryman; + if (deliveryman == null) { + modelDeliveryman = null; + } else { + modelDeliveryman = new Deliveryman(new Name(deliveryman)); + } + + return new Order(modelTag, modelName, modelPhone, modelAddress, modelDate, orderStatus, + modelFood, modelDeliveryman); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedOrder)) { + return false; + } + + XmlAdaptedOrder otherOrder = (XmlAdaptedOrder) other; + return tag.equals(otherOrder.tag) + && Objects.equals(name, otherOrder.name) + && Objects.equals(phone, otherOrder.phone) + && Objects.equals(address, otherOrder.address) + && Objects.equals(date, otherOrder.date) + && Objects.equals(status, otherOrder.status) + && food.equals(otherOrder.food) + && Objects.equals(deliveryman, otherOrder.deliveryman); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java deleted file mode 100644 index c03785e5700f..000000000000 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; - -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; - -/** - * JAXB-friendly version of the Person. - */ -public class XmlAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - @XmlElement(required = true) - private String name; - @XmlElement(required = true) - private String phone; - @XmlElement(required = true) - private String email; - @XmlElement(required = true) - private String address; - - @XmlElement - private List tagged = new ArrayList<>(); - - /** - * Constructs an XmlAdaptedPerson. - * This is the no-arg constructor that is required by JAXB. - */ - public XmlAdaptedPerson() {} - - /** - * Constructs an {@code XmlAdaptedPerson} with the given person details. - */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged = new ArrayList<>(tagged); - } - } - - /** - * Converts a given Person into this class for JAXB use. - * - * @param source future changes to this will not affect the created XmlAdaptedPerson - */ - public XmlAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged = source.getTags().stream() - .map(XmlAdaptedTag::new) - .collect(Collectors.toList()); - } - - /** - * Converts this jaxb-friendly adapted person object into the model's 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 (XmlAdaptedTag 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_NAME_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_PHONE_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_EMAIL_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_ADDRESS_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlAdaptedPerson)) { - return false; - } - - XmlAdaptedPerson otherPerson = (XmlAdaptedPerson) other; - return Objects.equals(name, otherPerson.name) - && Objects.equals(phone, otherPerson.phone) - && Objects.equals(email, otherPerson.email) - && Objects.equals(address, otherPerson.address) - && tagged.equals(otherPerson.tagged); - } -} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTag.java b/src/main/java/seedu/address/storage/XmlAdaptedTag.java index d3e2d8be9c4f..546970e61cfc 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedTag.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedTag.java @@ -17,7 +17,8 @@ public class XmlAdaptedTag { * Constructs an XmlAdaptedTag. * This is the no-arg constructor that is required by JAXB. */ - public XmlAdaptedTag() {} + public XmlAdaptedTag() { + } /** * Constructs a {@code XmlAdaptedTag} with the given {@code tagName}. @@ -38,7 +39,7 @@ public XmlAdaptedTag(Tag source) { /** * Converts this jaxb-friendly adapted tag object into the model's Tag object. * - * @throws IllegalValueException if there were any data constraints violated in the adapted person + * @throws IllegalValueException if there were any data constraints violated in the adapted common */ public Tag toModelType() throws IllegalValueException { if (!Tag.isValidTagName(tagName)) { diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java deleted file mode 100644 index ecf0e7ec23a8..000000000000 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as an xml file on the hard disk. - */ -public class XmlAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(XmlAddressBookStorage.class); - - private Path filePath; - - public XmlAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()} - * @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, - FileNotFoundException { - requireNonNull(filePath); - - if (!Files.exists(filePath)) { - logger.info("AddressBook file " + filePath + " not found"); - return Optional.empty(); - } - - XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(filePath); - try { - return Optional.of(xmlAddressBook.toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} - * @param filePath location of the data. Cannot be null - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - FileUtil.createIfMissing(filePath); - XmlFileStorage.saveDataToFile(filePath, new XmlSerializableAddressBook(addressBook)); - } - -} diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/seedu/address/storage/XmlFileStorage.java index d8f65dc036ab..40b9a485ca0b 100644 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ b/src/main/java/seedu/address/storage/XmlFileStorage.java @@ -7,33 +7,54 @@ import seedu.address.commons.exceptions.DataConversionException; import seedu.address.commons.util.XmlUtil; +import seedu.address.storage.user.XmlSerializableUsersList; /** - * Stores addressbook data in an XML file + * Stores orderbook data in an XML file */ public class XmlFileStorage { /** - * Saves the given addressbook data to the specified file. + * Saves the data for FoodZoom to the specified file. */ - public static void saveDataToFile(Path file, XmlSerializableAddressBook addressBook) + public static void saveFoodZoomDataToFile(Path file, XmlFoodZoom foodZoomData) throws FileNotFoundException { + try { + XmlUtil.saveDataToFile(file, foodZoomData); + } catch (JAXBException e) { + throw new AssertionError("Unexpected exception " + e.getMessage(), e); + } + } + + /** + * Returns the FoodZoom data from file + */ + public static XmlFoodZoom loadFoodZoomDataFromSaveFile(Path file) throws FileNotFoundException { + try { + return XmlUtil.getDataFromFile(file, XmlFoodZoom.class); + } catch (JAXBException e) { + throw new AssertionError("Unexpected exception " + e.getMessage(), e); + } + } + /** + * Saves the given users list data to the specified file. + */ + public static void saveUsersDataToFile(Path file, XmlSerializableUsersList usersList) throws FileNotFoundException { try { - XmlUtil.saveDataToFile(file, addressBook); + XmlUtil.saveDataToFile(file, usersList); } catch (JAXBException e) { throw new AssertionError("Unexpected exception " + e.getMessage(), e); } } /** - * Returns address book in the file or an empty address book + * Returns users list in the file or an empty users list. */ - public static XmlSerializableAddressBook loadDataFromSaveFile(Path file) throws DataConversionException, - FileNotFoundException { + public static XmlSerializableUsersList loadUsersDataFromSaveFile(Path file) throws DataConversionException, + FileNotFoundException { try { - return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); + return XmlUtil.getDataFromFile(file, XmlSerializableUsersList.class); } catch (JAXBException e) { throw new DataConversionException(e); } } - } diff --git a/src/main/java/seedu/address/storage/XmlFoodZoom.java b/src/main/java/seedu/address/storage/XmlFoodZoom.java new file mode 100644 index 000000000000..563b10e9f2d2 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlFoodZoom.java @@ -0,0 +1,40 @@ +package seedu.address.storage; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.OrderBook; +import seedu.address.model.ReadOnlyOrderBook; +import seedu.address.model.deliveryman.DeliverymenList; +import seedu.address.storage.deliveryman.XmlSerializableDeliverymenList; + +/** + * JAXB storage of FoodZoom information - namely an orderBook and a deliverymenList. + */ +@XmlRootElement(name = "foodzoom") +public class XmlFoodZoom { + @XmlElement(required = true) + private XmlSerializableOrderBook orderBook; + + @XmlElement(required = true) + private XmlSerializableDeliverymenList deliverymenList; + + public XmlFoodZoom() { + orderBook = new XmlSerializableOrderBook(); + deliverymenList = new XmlSerializableDeliverymenList(); + } + + public XmlFoodZoom(ReadOnlyOrderBook ordersSrc, DeliverymenList deliverymenSrc) { + orderBook = new XmlSerializableOrderBook(ordersSrc); + deliverymenList = new XmlSerializableDeliverymenList(deliverymenSrc); + } + + public OrderBook getOrderBook() throws IllegalValueException { + return orderBook.toModelType(); + } + + public DeliverymenList getDeliverymenList() throws IllegalValueException { + return deliverymenList.toModelType(); + } +} diff --git a/src/main/java/seedu/address/storage/XmlFoodZoomStorage.java b/src/main/java/seedu/address/storage/XmlFoodZoomStorage.java new file mode 100644 index 000000000000..178e836bf593 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlFoodZoomStorage.java @@ -0,0 +1,112 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.FileUtil; +import seedu.address.model.OrderBook; +import seedu.address.model.ReadOnlyOrderBook; +import seedu.address.model.deliveryman.DeliverymenList; + +/** + * A class to access FoodZoom data stored as an XML file. + */ +public class XmlFoodZoomStorage implements FoodZoomStorage { + private static final Logger logger = LogsCenter.getLogger(XmlFoodZoomStorage.class); + + private Path foodZoomFilePath; + private Optional deliverymenList; + private Optional orderBook; + + public XmlFoodZoomStorage(Path foodZoomFilePath) { + this.foodZoomFilePath = foodZoomFilePath; + } + + + public Path getFoodZoomFilePath() { + return foodZoomFilePath; + } + + public void saveFoodZoom(ReadOnlyOrderBook orderBook, DeliverymenList deliverymenList) throws IOException { + saveFoodZoom(orderBook, deliverymenList, foodZoomFilePath); + } + + /** + * Saves data to the XML file in the hard disk. + */ + public void saveFoodZoom(ReadOnlyOrderBook orderBook, DeliverymenList deliverymenList, Path filePath) + throws IOException { + requireNonNull(deliverymenList); + requireNonNull(orderBook); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + XmlFileStorage.saveFoodZoomDataToFile(filePath, new XmlFoodZoom(orderBook, deliverymenList)); + } + + @Override + public Optional readOrderBook() throws DataConversionException, IOException { + return readOrderBook(foodZoomFilePath); + } + + /** + * Similar to {@link #readOrderBook()} ()} + * + * @param filePath location of the data. Cannot be null + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readOrderBook(Path filePath) throws DataConversionException, + FileNotFoundException { + requireNonNull(filePath); + + if (!Files.exists(filePath)) { + logger.info("FoodZoom file " + filePath + " not found"); + return Optional.empty(); + } + + XmlFoodZoom xmlFoodZoom = XmlFileStorage.loadFoodZoomDataFromSaveFile(filePath); + try { + return Optional.of(xmlFoodZoom.getOrderBook()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public Optional readDeliverymenList() throws DataConversionException, IOException { + return readDeliverymenList(foodZoomFilePath); + } + + /** + * Similar to {@link #readDeliverymenList()} + * @param filePath location of the data. Cannot be null + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional readDeliverymenList(Path filePath) throws DataConversionException, + FileNotFoundException { + requireNonNull(filePath); + + if (!Files.exists(filePath)) { + logger.info("FoodZoom file " + filePath + " not found"); + return Optional.empty(); + } + + XmlFoodZoom xmlFoodZoom = XmlFileStorage.loadFoodZoomDataFromSaveFile(filePath); + try { + return Optional.of(xmlFoodZoom.getDeliverymenList()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } +} diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java deleted file mode 100644 index b85fa4a8f07e..000000000000 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ /dev/null @@ -1,71 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -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 XML format - */ -@XmlRootElement(name = "addressbook") -public class XmlSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - @XmlElement - private List persons; - - /** - * Creates an empty XmlSerializableAddressBook. - * This empty constructor is required for marshalling. - */ - public XmlSerializableAddressBook() { - persons = new ArrayList<>(); - } - - /** - * Conversion - */ - public XmlSerializableAddressBook(ReadOnlyAddressBook src) { - this(); - persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this addressbook into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated or duplicates in the - * {@code XmlAdaptedPerson}. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (XmlAdaptedPerson p : persons) { - Person person = p.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlSerializableAddressBook)) { - return false; - } - return persons.equals(((XmlSerializableAddressBook) other).persons); - } -} diff --git a/src/main/java/seedu/address/storage/XmlSerializableOrderBook.java b/src/main/java/seedu/address/storage/XmlSerializableOrderBook.java new file mode 100644 index 000000000000..6cca4cf96c76 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlSerializableOrderBook.java @@ -0,0 +1,69 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.OrderBook; +import seedu.address.model.ReadOnlyOrderBook; +import seedu.address.model.order.Order; + +/** + * An Immutable OrderBook that is serializable to XML format + */ +public class XmlSerializableOrderBook { + + public static final String MESSAGE_DUPLICATE_ORDER = "Orders list contains duplicate order(s)."; + + @XmlElement + private List orders; + + /** + * Creates an empty XmlSerializableOrderBook. + * This empty constructor is required for marshalling. + */ + public XmlSerializableOrderBook() { + orders = new ArrayList<>(); + } + + /** + * Conversion + */ + public XmlSerializableOrderBook(ReadOnlyOrderBook src) { + this(); + orders.addAll(src.getOrderList().stream().map(XmlAdaptedOrder::new).collect(Collectors.toList())); + } + + /** + * Converts this orderbook into the model's {@code OrderBook} object. + * + * @throws IllegalValueException if there were any data constraints violated or duplicates in the + * {@code XmlAdaptedOrder}. + */ + public OrderBook toModelType() throws IllegalValueException { + OrderBook orderBook = new OrderBook(); + for (XmlAdaptedOrder o : orders) { + Order order = o.toModelType(); + if (orderBook.hasOrder(order)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_ORDER); + } + orderBook.addOrder(order); + } + return orderBook; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlSerializableOrderBook)) { + return false; + } + return orders.equals(((XmlSerializableOrderBook) other).orders); + } +} diff --git a/src/main/java/seedu/address/storage/deliveryman/DeliverymenListStorage.java b/src/main/java/seedu/address/storage/deliveryman/DeliverymenListStorage.java new file mode 100644 index 000000000000..91dbc5048682 --- /dev/null +++ b/src/main/java/seedu/address/storage/deliveryman/DeliverymenListStorage.java @@ -0,0 +1,40 @@ +package seedu.address.storage.deliveryman; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.deliveryman.DeliverymenList; + +/** + * Represents the Storage interface for DeliverymenList + */ +public interface DeliverymenListStorage { + Path getDeliverymenListFilePath(); + + /** + * Returns a list of deliveryman as a {@link DeliverymenList}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readDeliverymenList() throws DataConversionException, IOException; + + /** + * @see #getDeliverymanFilePath() + */ + Optional readDeliverymenList(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link DeliverymenList} to the storage. + * @param deliverymanList cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveDeliverymenList(DeliverymenList deliverymanList) throws IOException; + + /** + * @see #saveDeliverymenList(DeliverymenList) + */ + void saveDeliverymenList(DeliverymenList deliverymanList, Path filePath) throws IOException; +} diff --git a/src/main/java/seedu/address/storage/deliveryman/XmlAdaptedDeliveryman.java b/src/main/java/seedu/address/storage/deliveryman/XmlAdaptedDeliveryman.java new file mode 100644 index 000000000000..f764495ec22a --- /dev/null +++ b/src/main/java/seedu/address/storage/deliveryman/XmlAdaptedDeliveryman.java @@ -0,0 +1,126 @@ +package seedu.address.storage.deliveryman; + +import static seedu.address.model.TaggedObject.MESSAGE_INVALID_ID; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlID; +import javax.xml.bind.annotation.XmlIDREF; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.common.Name; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.order.Order; +import seedu.address.storage.XmlAdaptedOrder; + +/** + * Represents the XML for storage of Deliveryman + */ +public class XmlAdaptedDeliveryman { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Deliveryman's %s field is missing!"; + + @XmlAttribute + @XmlID + private String tag; + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + @XmlIDREF + private List orders = new ArrayList<>(); + + public XmlAdaptedDeliveryman() {} + + /** + * Constructs an {@code XmlAdapterDeliveryman} with the given common details. + */ + public XmlAdaptedDeliveryman(String tag, String name, List orders) { + this.tag = tag; + this.name = name; + if (orders == null) { + this.orders = new ArrayList<>(); + } else { + this.orders = new ArrayList<>(orders); + } + } + + /** + * Constructs an {@code XmlAdapterDeliveryman} with the given common details. + */ + public XmlAdaptedDeliveryman(String name) { + this.tag = UUID.randomUUID().toString(); + this.name = name; + } + + /** + * Converts a given deliveryman into this class for JAXB use. + * + * @param source + */ + public XmlAdaptedDeliveryman(Deliveryman source) { + tag = source.getTag().toString(); + name = source.getName().fullName; + orders = source.getOrders().stream() + .map(XmlAdaptedOrder::new) + .collect(Collectors.toList()); + } + + /** + * Converts this jaxb-friendly adapted deliveryman object into the model's Deliveryman object. + * + * @throws IllegalValueException If there were any data constraints violated. + */ + public Deliveryman toModelType() 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_NAME_CONSTRAINTS); + } + + if (tag == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Tag")); + } + + UUID modelTag; + + try { + modelTag = UUID.fromString(tag); + } catch (NumberFormatException e) { + throw new IllegalValueException(MESSAGE_INVALID_ID); + } + + final Name modelName = new Name(name); + + final List orderStore = new ArrayList<>(); + for (XmlAdaptedOrder orderItem : orders) { + orderStore.add(orderItem.toModelType()); + } + final Set modelOrder = new HashSet<>(orderStore); + + return new Deliveryman(modelTag, modelName, modelOrder); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedDeliveryman)) { + return false; + } + + XmlAdaptedDeliveryman otherDman = (XmlAdaptedDeliveryman) other; + return tag.equals(otherDman.tag) + && Objects.equals(name, otherDman.name); + } +} diff --git a/src/main/java/seedu/address/storage/deliveryman/XmlSerializableDeliverymenList.java b/src/main/java/seedu/address/storage/deliveryman/XmlSerializableDeliverymenList.java new file mode 100644 index 000000000000..ae8b528433e9 --- /dev/null +++ b/src/main/java/seedu/address/storage/deliveryman/XmlSerializableDeliverymenList.java @@ -0,0 +1,68 @@ +package seedu.address.storage.deliveryman; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.deliveryman.DeliverymenList; + +/** + * An Immutable DeliverymenList that is serializable to XML format + */ +public class XmlSerializableDeliverymenList { + + public static final String MESSAGE_DUPLICATE_DELIVERYMAN = "Deliverymen List contains duplicate deliverymen."; + + @XmlElement + private List deliverymen; + + /** + * Creates an empty XmlSerializableDeliverymenList. + * Required for marshalling. + */ + public XmlSerializableDeliverymenList() { + deliverymen = new ArrayList<>(); + } + + /** + * Converts DeliverymenList to Serializable + */ + public XmlSerializableDeliverymenList(DeliverymenList src) { + this(); + deliverymen.addAll(src.getDeliverymenList().stream().map(XmlAdaptedDeliveryman::new) + .collect(Collectors.toList())); + } + + /** + * Converts this deliverymen list into the model's {@code DeliverymenList} object. + * + * @throws IllegalValueException if there were any data constraints violated or dupicates + */ + public DeliverymenList toModelType() throws IllegalValueException { + DeliverymenList deliverymenList = new DeliverymenList(); + for (XmlAdaptedDeliveryman d : deliverymen) { + Deliveryman dMan = d.toModelType(); + if (deliverymenList.hasDeliveryman(dMan)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_DELIVERYMAN); + } + deliverymenList.addDeliveryman(dMan); + } + return deliverymenList; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlSerializableDeliverymenList)) { + return false; + } + return deliverymen.equals(((XmlSerializableDeliverymenList) other).deliverymen); + } +} diff --git a/src/main/java/seedu/address/storage/user/UsersListStorage.java b/src/main/java/seedu/address/storage/user/UsersListStorage.java new file mode 100644 index 000000000000..fc1a80066f4f --- /dev/null +++ b/src/main/java/seedu/address/storage/user/UsersListStorage.java @@ -0,0 +1,45 @@ +package seedu.address.storage.user; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.ReadOnlyUsersList; + +/** + * Represents a storage for {@link seedu.address.model.UsersList}. + */ +public interface UsersListStorage { + /** + * Returns the file path of the data file. + */ + Path getUsersListFilePath(); + + /** + * Returns UsersList data as a {@link ReadOnlyUsersList}. + * 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 readUsersList() throws DataConversionException, IOException; + + /** + * @see #getUsersListFilePath() + */ + Optional readUsersList(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyUsersList} to the storage. + * + * @param usersList cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveUsersList(ReadOnlyUsersList usersList) throws IOException; + + /** + * @see #saveUsersList(ReadOnlyUsersList) + */ + void saveUsersList(ReadOnlyUsersList usersList, Path filePath) throws IOException; +} diff --git a/src/main/java/seedu/address/storage/user/XmlAdaptedUser.java b/src/main/java/seedu/address/storage/user/XmlAdaptedUser.java new file mode 100644 index 000000000000..6c325f885d4c --- /dev/null +++ b/src/main/java/seedu/address/storage/user/XmlAdaptedUser.java @@ -0,0 +1,105 @@ +package seedu.address.storage.user; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.common.Name; +import seedu.address.model.common.Password; +import seedu.address.model.common.Username; +import seedu.address.model.user.User; + +/** + * JAXB-friendly version of the User. + */ +public class XmlAdaptedUser { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "User's %s field is missing!"; + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private String username; + @XmlElement(required = true) + private String password; + + /** + * Constructs an XmlAdaptedUser. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedUser() { + } + + + /** + * Constructs an {@code XmlAdaptedUser} with the given common details. + */ + public XmlAdaptedUser(String name, String username, String password) { + this.name = name; + this.username = username; + this.password = password; + } + + /** + * Converts a given User into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedUser + */ + public XmlAdaptedUser(User source) { + name = source.getName().fullName; + username = source.getUsername().value; + password = source.getPassword().value; + } + + /** + * Converts this jaxb-friendly adapted common object into the model's User object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted user + */ + public User toModelType() 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_NAME_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (username == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Username.class.getSimpleName())); + } + if (!Username.isValidUsername(username)) { + throw new IllegalValueException(Username.MESSAGE_USERNAME_CONSTRAINTS); + } + final Username modelUsername = new Username(username); + + if (password == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Password.class.getSimpleName())); + } + if (!Password.isValidPassword(password)) { + throw new IllegalValueException(Password.MESSAGE_PASSWORD_CONSTRAINTS); + } + final Password modelPassword = new Password(password); + return new User(modelName, modelUsername, modelPassword); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedUser)) { + return false; + } + + XmlAdaptedUser otherUser = (XmlAdaptedUser) other; + return Objects.equals(name, otherUser.name) + && Objects.equals(username, otherUser.username) + && Objects.equals(password, otherUser.password); + } +} diff --git a/src/main/java/seedu/address/storage/user/XmlSerializableUsersList.java b/src/main/java/seedu/address/storage/user/XmlSerializableUsersList.java new file mode 100644 index 000000000000..095279a34fea --- /dev/null +++ b/src/main/java/seedu/address/storage/user/XmlSerializableUsersList.java @@ -0,0 +1,73 @@ +package seedu.address.storage.user; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.ReadOnlyUsersList; +import seedu.address.model.UsersList; +import seedu.address.model.user.User; + +/** + * An Immutable User that is serializable to XML format + */ +@XmlRootElement(name = "userslist") +public class XmlSerializableUsersList { + + public static final String MESSAGE_DUPLICATE_USER = "User list contains duplicate user(s)."; + + @XmlElement + private List users; + + /** + * Creates an empty XmlSerializableAddressBook. + * This empty constructor is required for marshalling. + */ + public XmlSerializableUsersList() { + users = new ArrayList<>(); + } + + /** + * Conversion + */ + public XmlSerializableUsersList(ReadOnlyUsersList src) { + this(); + users.addAll(src.getUserList().stream().map(XmlAdaptedUser::new).collect(Collectors.toList())); + } + + + /** + * Converts this addressbook into the model's {@code AddressBook} object. + * + * @throws IllegalValueException if there were any data constraints violated or duplicates in the + * {@code XmlAdaptedPerson}. + */ + public UsersList toModelType() throws IllegalValueException { + UsersList usersList = new UsersList(); + for (XmlAdaptedUser u : users) { + User user = u.toModelType(); + if (usersList.hasUser(user)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_USER); + } + usersList.addUser(user); + } + return usersList; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlSerializableUsersList)) { + return false; + } + return users.equals(((XmlSerializableUsersList) other).users); + } + +} diff --git a/src/main/java/seedu/address/storage/user/XmlUsersListStorage.java b/src/main/java/seedu/address/storage/user/XmlUsersListStorage.java new file mode 100644 index 000000000000..c9add17bb8cf --- /dev/null +++ b/src/main/java/seedu/address/storage/user/XmlUsersListStorage.java @@ -0,0 +1,83 @@ +package seedu.address.storage.user; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.FileUtil; +import seedu.address.model.ReadOnlyUsersList; +import seedu.address.storage.XmlFileStorage; + +/** + * A class to access UsersList data stored as an xml file on the hard disk. + */ +public class XmlUsersListStorage implements UsersListStorage { + + private static final Logger logger = LogsCenter.getLogger(XmlUsersListStorage.class); + + private Path filePath; + + public XmlUsersListStorage(Path filePath) { + this.filePath = filePath; + } + + @Override + public Path getUsersListFilePath() { + return filePath; + } + + @Override + public Optional readUsersList() throws DataConversionException, IOException { + return readUsersList(filePath); + } + + /** + * Similar to {@link #readUsersList()} + * + * @param filePath location of the data. Cannot be null + * @throws DataConversionException if the file is not in the correct format. + */ + @Override + public Optional readUsersList(Path filePath) throws DataConversionException, IOException { + requireNonNull(filePath); + + if (!Files.exists(filePath)) { + logger.info("UsersList file " + filePath + " not found"); + return Optional.empty(); + } + + XmlSerializableUsersList xmlUsersList = XmlFileStorage.loadUsersDataFromSaveFile(filePath); + try { + return Optional.of(xmlUsersList.toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void saveUsersList(ReadOnlyUsersList usersList) throws IOException { + saveUsersList(usersList, filePath); + } + + /** + * Similar to {@link #saveUsersList(ReadOnlyUsersList)} + * + * @param filePath location of the data. Cannot be null + */ + @Override + public void saveUsersList(ReadOnlyUsersList usersList, Path filePath) throws IOException { + requireNonNull(usersList); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + XmlFileStorage.saveUsersDataToFile(filePath, new XmlSerializableUsersList(usersList)); + } +} diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java index b43de90a2b9f..5aedae1cebfc 100644 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ b/src/main/java/seedu/address/ui/BrowserPanel.java @@ -12,8 +12,8 @@ import javafx.scene.web.WebView; import seedu.address.MainApp; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; +import seedu.address.commons.events.ui.OrderPanelSelectionChangedEvent; +import seedu.address.model.order.Order; /** * The Browser Panel of the App. @@ -41,8 +41,8 @@ public BrowserPanel() { registerAsAnEventHandler(this); } - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); + private void loadOrderPage(Order order) { + loadPage(SEARCH_PAGE_URL + order.getName().fullName); } public void loadPage(String url) { @@ -65,8 +65,8 @@ public void freeResources() { } @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { + private void handleOrderPanelSelectionChangedEvent(OrderPanelSelectionChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection()); + loadOrderPage(event.getNewSelection()); } } diff --git a/src/main/java/seedu/address/ui/Dashboard.java b/src/main/java/seedu/address/ui/Dashboard.java new file mode 100644 index 000000000000..90c1336d9161 --- /dev/null +++ b/src/main/java/seedu/address/ui/Dashboard.java @@ -0,0 +1,45 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; + +/** + * Panel containing the main split pane. + */ +public class Dashboard extends UiPart { + + private static final String FXML = "Dashboard.fxml"; + private final Logger logger = LogsCenter.getLogger(LoginPanel.class); + + private Display display; + private OrderListPanel orderListPanel; + private DeliverymanListPanel deliveryMenListPanel; + private LoginPanel loginPanel; + + @FXML + private StackPane displayPlaceholder; + + @FXML + private StackPane orderListPanelPlaceholder; + + @FXML + private StackPane deliveryMenListPanelPlaceholder; + + + public Dashboard(Logic logic) { + super(FXML); + display = new Display(logic.getFilteredOrderList()); + displayPlaceholder.getChildren().add(display.getRoot()); + + orderListPanel = new OrderListPanel(logic.getFilteredOrderList()); + orderListPanelPlaceholder.getChildren().add(orderListPanel.getRoot()); + + deliveryMenListPanel = new DeliverymanListPanel(logic.getFilteredDeliverymanList()); + deliveryMenListPanelPlaceholder.getChildren().add(deliveryMenListPanel.getRoot()); + } +} diff --git a/src/main/java/seedu/address/ui/DeliverymanCard.java b/src/main/java/seedu/address/ui/DeliverymanCard.java new file mode 100644 index 000000000000..8684bd78f5f3 --- /dev/null +++ b/src/main/java/seedu/address/ui/DeliverymanCard.java @@ -0,0 +1,70 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import seedu.address.model.deliveryman.Deliveryman; + +/** + * An UI component that displays information of a {@code Order}. + */ +public class DeliverymanCard extends UiPart { + + private static final String FXML = "DeliverymanListCard.fxml"; + private static final String BUSY_LABEL_CLASS = "busy"; + private static final String AVAILABLE_LABEL_CLASS = "available"; + + /** + * 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 OrderBook level 4 + */ + + public final Deliveryman deliveryman; + + @FXML + private VBox cardPane; + @FXML + private Label name; + @FXML + private Label deliverymanIndicator; + + public DeliverymanCard(Deliveryman deliveryman, int displayedIndex) { + super(FXML); + this.deliveryman = deliveryman; + name.setText(deliveryman.getName().fullName); + setDeliverymanStatus(); + } + + private void setDeliverymanStatus() { + if (deliveryman.getOrders().size() > 0) { + deliverymanIndicator.getStyleClass().clear(); + deliverymanIndicator.getStyleClass().add(BUSY_LABEL_CLASS); + deliverymanIndicator.setText("Assigned: " + deliveryman.getOrders().size()); + } else { + deliverymanIndicator.getStyleClass().clear(); + deliverymanIndicator.getStyleClass().add(AVAILABLE_LABEL_CLASS); + deliverymanIndicator.setText("Available"); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeliverymanCard)) { + return false; + } + + // state check + DeliverymanCard card = (DeliverymanCard) other; + return deliveryman.equals(card.deliveryman); + } +} diff --git a/src/main/java/seedu/address/ui/DeliverymanListPanel.java b/src/main/java/seedu/address/ui/DeliverymanListPanel.java new file mode 100644 index 000000000000..a1bc04cce581 --- /dev/null +++ b/src/main/java/seedu/address/ui/DeliverymanListPanel.java @@ -0,0 +1,96 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +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.commons.events.ui.BackToHomeEvent; +import seedu.address.commons.events.ui.DeliveryManPanelSelectionChangedEvent; +import seedu.address.commons.events.ui.JumpToDeliveryManListRequestEvent; +import seedu.address.commons.events.ui.OrderPanelSelectionChangedEvent; +import seedu.address.model.deliveryman.Deliveryman; + +/** + * Panel containing the list of orders. + */ +public class DeliverymanListPanel extends UiPart { + private static final String FXML = "DeliverymanListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(DeliverymanListPanel.class); + + @FXML + private ListView deliverymanListView; + + public DeliverymanListPanel(ObservableList deliveryManList) { + super(FXML); + + setConnections(deliveryManList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList orderList) { + deliverymanListView.setItems(orderList); + deliverymanListView.setCellFactory(listView -> new DeliveryManListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setEventHandlerForSelectionChangeEvent() { + deliverymanListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in delivery man list panel changed to : '" + newValue + "'"); + raise(new DeliveryManPanelSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code OrderCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + deliverymanListView.scrollTo(index); + deliverymanListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToDeliveryManListRequestEvent(JumpToDeliveryManListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + @Subscribe + private void handleOrderPanelSelectionChangedEvent(OrderPanelSelectionChangedEvent event) { + deliverymanListView.getSelectionModel().clearSelection(); + } + + @Subscribe + private void handleBackToHomeRequest(BackToHomeEvent event) { + deliverymanListView.getSelectionModel().clearSelection(); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Order} using a {@code OrderCard}. + */ + class DeliveryManListViewCell extends ListCell { + @Override + protected void updateItem(Deliveryman deliveryman, boolean empty) { + super.updateItem(deliveryman, empty); + + if (empty || deliveryman == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new DeliverymanCard(deliveryman, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/Display.java b/src/main/java/seedu/address/ui/Display.java new file mode 100644 index 000000000000..5329205441bf --- /dev/null +++ b/src/main/java/seedu/address/ui/Display.java @@ -0,0 +1,338 @@ +package seedu.address.ui; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import java.util.TreeMap; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.events.ui.BackToHomeEvent; +import seedu.address.commons.events.ui.DeliveryManPanelSelectionChangedEvent; +import seedu.address.commons.events.ui.OrderPanelSelectionChangedEvent; +import seedu.address.model.order.Food; +import seedu.address.model.order.Order; +import seedu.address.ui.display.DeliverymanDisplayCard; +import seedu.address.ui.display.OrderDisplayCard; + + +/** + * Panel containing the main display - map and side content. + */ +public class Display extends UiPart { + + private static final String FXML = "Display.fxml"; + private final Logger logger = LogsCenter.getLogger(Display.class); + + @FXML + private StackPane mapWrapper; + + @FXML + private StackPane displayPanelPlaceholder; + + @FXML + private StackPane statisticsWrapper; + + private StatisticsPanel statisticsPanel; + private TreeMap orderHistory; + private HashMap purchaseHistory; + private double progress; + private int total; + + private MapPanel mapPanel; + private ObservableList orderList; + private HashMap directory; + + + /** + * Constructor for this panel. Process information related to order and updates respective UI components + * + * @param orderList the current list of orders in-memory + */ + public Display(ObservableList orderList) { + super(FXML); + this.orderList = orderList; + this.progress = 0; + this.total = orderList.size(); + + fillInnerParts(); + setupMap(); + setupStatistics(); + + this.orderList.addListener((ListChangeListener.Change change) -> { + while (change.next()) { + + if (change.wasUpdated()) { + + this.progress = 0; + this.total = 0; + + directory = new HashMap<>(); + updateMapCache(change.getList()); + + orderHistory = new TreeMap<>(); + updateOrderHistory(change.getList()); + + purchaseHistory = new HashMap<>(); + addFoodItems(change.getList()); + + trackProgress(change.getList(), false); + total = change.getList().size(); + + } else { + removeFromMapCache(change.getRemoved()); + updateMapCache(change.getAddedSubList()); + + removeFromOrderHistory(change.getRemoved()); + updateOrderHistory(change.getAddedSubList()); + + removeFoodItems(change.getRemoved()); + addFoodItems(change.getAddedSubList()); + + trackProgress(change.getRemoved(), true); + trackProgress(change.getAddedSubList(), false); + + total += change.getAddedSubList().size(); + total -= change.getRemoved().size(); + } + + mapPanel.clear(); + mapPanel.initialise(directory); + + statisticsPanel.clear(); + statisticsPanel.initialize(orderHistory); + statisticsPanel.updateLabels(total, progress / total, getTrendingFood()); + } + }); + + registerAsAnEventHandler(this); + } + + /** + * Initializes and sets up the variables needed for map component + */ + private void setupMap() { + directory = new HashMap<>(); + updateMapCache(orderList); + mapPanel.initialise(directory); + } + + /** + * Initializes and sets up the variables needed for statistics component + */ + private void setupStatistics() { + orderHistory = new TreeMap<>(); + purchaseHistory = new HashMap<>(); + this.progress = 0; + + logger.info(progress + " " + total); + + addFoodItems(orderList); + trackProgress(orderList, false); + updateOrderHistory(orderList); + + statisticsPanel.initialize(orderHistory); + + logger.info(progress + " " + total); + + statisticsPanel.updateLabels(total, progress / total, getTrendingFood()); + } + + /** + * Fills the panel with the map and statistics + */ + private void fillInnerParts() { + mapPanel = new MapPanel(); + mapWrapper.getChildren().add(mapPanel.getRoot()); + + statisticsPanel = new StatisticsPanel(); + statisticsWrapper.getChildren().add(statisticsPanel.getRoot()); + } + + /** + * This updates the progress bar in the statistics panel with % of pending orders + * + * @param changeList list of orders that have to be changed + * @param toRemove a flag to indicate whether to update or remove + */ + public void trackProgress(List changeList, boolean toRemove) { + for (Order o : changeList) { + if (o.getOrderStatus().toString().equals("PENDING")) { + if (toRemove) { + progress--; + } else { + progress++; + } + } + } + } + + /** + * Looks through hashmap to find the most popular food item ordered + * + * @return a String that represents the food item ordered the most + */ + private String getTrendingFood() { + String bestFood = ""; + int bestVal = -1; + + for (String food : purchaseHistory.keySet()) { + if (purchaseHistory.get(food) > bestVal) { + bestVal = purchaseHistory.get(food); + bestFood = food; + } + } + + return bestFood; + } + + /** + * Adds food items onto the purchase history hashmap once there is a change + * + * @param changeList orders that were changed + */ + private void addFoodItems(List changeList) { + for (Order o : changeList) { + Set foodList = o.getFood(); + for (Food item : foodList) { + String foodKey = item.toString(); + if (purchaseHistory.containsKey(foodKey)) { + purchaseHistory.put(foodKey, purchaseHistory.get(foodKey) + 1); + } else { + purchaseHistory.put(foodKey, 1); + } + } + } + } + + /** + * Removes food items from the purchase history hashmap once there is a change + * + * @param changeList orders that were changed + */ + private void removeFoodItems(List changeList) { + for (Order o : changeList) { + Set foodList = o.getFood(); + for (Food item : foodList) { + String foodKey = item.toString(); + if (purchaseHistory.containsKey(foodKey)) { + if (purchaseHistory.get(foodKey) <= 1) { + purchaseHistory.remove(foodKey); + } else { + purchaseHistory.put(foodKey, purchaseHistory.get(foodKey) - 1); + } + } + } + } + } + + /** + * Removes addresses from the directory hashmap once there is a change + * + * @param changeList orders that were changed + */ + private void removeFromMapCache(List changeList) { + for (Order o : changeList) { + if (o.getOrderStatus().toString().equals("PENDING")) { + String postalCode = o.getAddress().getPostalCode(); + String postalCodeKey = postalCode.substring(0, 2); + if (directory.containsKey(postalCodeKey)) { + if (directory.get(postalCodeKey) <= 1) { + directory.remove(postalCodeKey); + } else { + directory.put(postalCodeKey, directory.get(postalCodeKey) - 1); + } + } + } + } + } + + /** + * Adds addresses onto the directory hashmap once there is a change + * + * @param changeList orders that were changed + */ + private void updateMapCache(List changeList) { + for (Order o : changeList) { + if (o.getOrderStatus().toString().equals("PENDING")) { + String postalCode = o.getAddress().getPostalCode(); + String postalCodeKey = postalCode.substring(0, 2); + if (directory.containsKey(postalCodeKey)) { + directory.put(postalCodeKey, directory.get(postalCodeKey) + 1); + } else { + directory.put(postalCodeKey, 1); + } + } + } + } + + /** + * Adds orders onto the orderHistory treemap once there is a change + * + * @param changeList orders that were changed + */ + private void updateOrderHistory(List changeList) { + for (Order o : changeList) { + Date dateKey = o.getDate().getShortenedDate(); + logger.info("ADDED " + o.toString()); + if (orderHistory.containsKey(dateKey)) { + orderHistory.put(dateKey, orderHistory.get(dateKey) + 1); + } else { + orderHistory.put(dateKey, 1); + } + } + + logger.info(orderHistory.toString()); + } + + /** + * Removes orders from the orderHistory treemap once there is a change + * + * @param changeList orders that were changed + */ + private void removeFromOrderHistory(List changeList) { + for (Order o : changeList) { + Date dateKey = o.getDate().getShortenedDate(); + logger.info("REMOVED " + o.toString()); + if (orderHistory.containsKey(dateKey)) { + if (orderHistory.get(dateKey) <= 1) { + orderHistory.remove(dateKey); + } else { + orderHistory.put(dateKey, orderHistory.get(dateKey) - 1); + } + } + } + + logger.info(orderHistory.toString()); + } + + @Subscribe + public void handleBackToHomeRequest(BackToHomeEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + + statisticsPanel = new StatisticsPanel(); + displayPanelPlaceholder.getChildren().setAll(statisticsPanel.getRoot()); + + setupStatistics(); + } + + @Subscribe + public void handleOrderPanelSelectionChangedEvent(OrderPanelSelectionChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + displayPanelPlaceholder.getChildren().setAll(new OrderDisplayCard(event.getNewSelection()).getRoot()); + } + + @Subscribe + public void handleDeliveryPanelSelectionChangedEvent(DeliveryManPanelSelectionChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + displayPanelPlaceholder.getChildren().setAll(new DeliverymanDisplayCard(event.getNewSelection()).getRoot()); + } +} diff --git a/src/main/java/seedu/address/ui/LoginPanel.java b/src/main/java/seedu/address/ui/LoginPanel.java new file mode 100644 index 000000000000..29ae9176e8b6 --- /dev/null +++ b/src/main/java/seedu/address/ui/LoginPanel.java @@ -0,0 +1,19 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; + +/** + * Panel containing the login screen. + */ +public class LoginPanel extends UiPart { + + private static final String FXML = "LoginPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(LoginPanel.class); + + public LoginPanel() { + super(FXML); + } +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 0e361a4d7baf..f1d616691e4b 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -10,11 +10,14 @@ import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; +import javafx.scene.layout.AnchorPane; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import seedu.address.commons.core.Config; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.events.model.UserLoggedInEvent; +import seedu.address.commons.events.model.UserLoggedOutEvent; import seedu.address.commons.events.ui.ExitAppRequestEvent; import seedu.address.commons.events.ui.ShowHelpRequestEvent; import seedu.address.logic.Logic; @@ -27,6 +30,8 @@ public class MainWindow extends UiPart { private static final String FXML = "MainWindow.fxml"; + private static final String DISPLAYING_LOGGED_IN_PANEL = "Displaying logged in panel"; + private static final String HIDE_LOGGED_IN_PANEL = "Hiding logged in panel"; private final Logger logger = LogsCenter.getLogger(getClass()); @@ -34,14 +39,12 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; - private PersonListPanel personListPanel; private Config config; private UserPrefs prefs; private HelpWindow helpWindow; - @FXML - private StackPane browserPlaceholder; + private LoginPanel loginPanel; + private Dashboard loggedInPanel; @FXML private StackPane commandBoxPlaceholder; @@ -49,15 +52,16 @@ public class MainWindow extends UiPart { @FXML private MenuItem helpMenuItem; - @FXML - private StackPane personListPanelPlaceholder; - @FXML private StackPane resultDisplayPlaceholder; @FXML private StackPane statusbarPlaceholder; + @FXML + private AnchorPane mainDisplayPlaceholder; + + public MainWindow(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { super(FXML, primaryStage); @@ -87,6 +91,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -119,20 +124,36 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); - - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); - ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getFoodZoomFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(logic); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + loginPanel = new LoginPanel(); + mainDisplayPlaceholder.getChildren().add(loginPanel.getRoot()); + } + + /** + * Display logged in panel after login successful. + */ + void displayLoggedInPanel() { + mainDisplayPlaceholder.getChildren().removeAll(loginPanel.getRoot()); + + loggedInPanel = new Dashboard(logic); + mainDisplayPlaceholder.getChildren().add(loggedInPanel.getRoot()); + } + + /** + * Hide logged in panel after logout successful. + */ + void hideLoggedInPanel() { + mainDisplayPlaceholder.getChildren().removeAll(loggedInPanel.getRoot()); + + mainDisplayPlaceholder.getChildren().add(loginPanel.getRoot()); } void hide() { @@ -187,17 +208,21 @@ private void handleExit() { raise(new ExitAppRequestEvent()); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + @Subscribe + private void handleShowHelpEvent(ShowHelpRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + handleHelp(); } - void releaseResources() { - browserPanel.freeResources(); + @Subscribe + private void handleUserLoggedInEvent(UserLoggedInEvent event) { + logger.info(DISPLAYING_LOGGED_IN_PANEL); + displayLoggedInPanel(); } @Subscribe - private void handleShowHelpEvent(ShowHelpRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - handleHelp(); + private void handleUserLoggedOutEvent(UserLoggedOutEvent event) { + logger.info(HIDE_LOGGED_IN_PANEL); + hideLoggedInPanel(); } } diff --git a/src/main/java/seedu/address/ui/MapPanel.java b/src/main/java/seedu/address/ui/MapPanel.java new file mode 100644 index 000000000000..8482cb5f902c --- /dev/null +++ b/src/main/java/seedu/address/ui/MapPanel.java @@ -0,0 +1,75 @@ +package seedu.address.ui; + +import static seedu.address.ui.util.MapDataUtil.DISTRICT_CACHE; +import static seedu.address.ui.util.MapDataUtil.POSTAL_CODE_CACHE; + +import java.util.HashMap; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; + +/** + * An UI component that displays a map along with placeholders for pending {@code Order}. + */ +public class MapPanel extends UiPart { + + private static final String FXML = "MapPanel.fxml"; + private static final String PLACEHOLDER_IMAGE_PATH = "/images/placeholder.png"; + + private final Logger logger = LogsCenter.getLogger(MapPanel.class); + + private final Image placeholder = new Image(getClass().getResource(PLACEHOLDER_IMAGE_PATH).toExternalForm()); + private ImageView placeholderContainer; + + @FXML + private GridPane mapPane; + + public MapPanel() { + super(FXML); + } + + /** + * Initialises map with all pending orders using a cache + * @param orderMap map of pending orders where key is postal code and value is counter + */ + public void initialise(HashMap orderMap) { + // for each postal code + for (String postalCodeKey : orderMap.keySet()) { + + if (POSTAL_CODE_CACHE.containsKey(postalCodeKey)) { + + int regionCode = POSTAL_CODE_CACHE.get(postalCodeKey); + Integer[] coordinates = DISTRICT_CACHE.get(regionCode); + + placeholderContainer = new ImageView(placeholder); + + int count = orderMap.get(postalCodeKey); + + // set a limit on count + if (count > 25) { + count = 25; + } + + int increment = 16 + count * 2; + + placeholderContainer.setPreserveRatio(true); + placeholderContainer.setFitHeight(increment); + placeholderContainer.setFitWidth(increment); + + mapPane.add(placeholderContainer, coordinates[1], coordinates[0]); + } + } + } + + /** + * Clears all placeholders from map + */ + public void clear() { + mapPane.getChildren().clear(); + } +} diff --git a/src/main/java/seedu/address/ui/OrderCard.java b/src/main/java/seedu/address/ui/OrderCard.java new file mode 100644 index 000000000000..e3e7bacb4fb0 --- /dev/null +++ b/src/main/java/seedu/address/ui/OrderCard.java @@ -0,0 +1,89 @@ +package seedu.address.ui; + +import java.util.stream.Collectors; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import javafx.scene.text.Text; +import seedu.address.model.order.Food; +import seedu.address.model.order.Order; + +/** + * An UI component that displays information of a {@code Order}. + */ +public class OrderCard extends UiPart { + + public static final String STATUS_PENDING = "PENDING"; + public static final String STATUS_ONGOING = "ONGOING"; + public static final String STATUS_COMPLETED = "COMPLETED"; + private static final String FXML = "OrderListCard.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 OrderBook level 4 + */ + + public final Order order; + + @FXML + private Text id; + @FXML + private Label address; + @FXML + private Label foodList; + @FXML + private Label orderDate; + @FXML + private Label orderStatus; + + public OrderCard(Order order, int displayedIndex) { + super(FXML); + this.order = order; + id.setText("#" + displayedIndex); + + address.setText(order.getAddress().value); + + foodList.setText(String.join(", ", + order.getFood().stream().map(Food::toString).collect(Collectors.toSet()))); + + orderDate.setText(order.getDate().toString()); + + orderStatus.setText(order.getOrderStatus().toString().substring(0, 1).toUpperCase() + + order.getOrderStatus().toString().substring(1).toLowerCase()); + setOrderStatusColor(); + } + + private void setOrderStatusColor() { + orderStatus.getStyleClass().clear(); + if (order.getOrderStatus().toString().equals(STATUS_PENDING)) { + orderStatus.getStyleClass().add("pending"); + } else if (order.getOrderStatus().toString().equals(STATUS_ONGOING)) { + orderStatus.getStyleClass().add("ongoing"); + } else if (order.getOrderStatus().toString().equals(STATUS_COMPLETED)) { + orderStatus.getStyleClass().add("completed"); + } + } + + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OrderCard)) { + return false; + } + + // state check + OrderCard card = (OrderCard) other; + return id.getText().equals(card.id.getText()) + && order.equals(card.order); + } +} diff --git a/src/main/java/seedu/address/ui/OrderListPanel.java b/src/main/java/seedu/address/ui/OrderListPanel.java new file mode 100644 index 000000000000..50abcb99e153 --- /dev/null +++ b/src/main/java/seedu/address/ui/OrderListPanel.java @@ -0,0 +1,96 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +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.commons.events.ui.BackToHomeEvent; +import seedu.address.commons.events.ui.DeliveryManPanelSelectionChangedEvent; +import seedu.address.commons.events.ui.JumpToOrderListRequestEvent; +import seedu.address.commons.events.ui.OrderPanelSelectionChangedEvent; +import seedu.address.model.order.Order; + +/** + * Panel containing the list of orders. + */ +public class OrderListPanel extends UiPart { + private static final String FXML = "OrderListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(OrderListPanel.class); + + @FXML + private ListView orderListView; + + public OrderListPanel(ObservableList orderList) { + super(FXML); + + setConnections(orderList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList orderList) { + orderListView.setItems(orderList); + orderListView.setCellFactory(listView -> new OrderListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setEventHandlerForSelectionChangeEvent() { + orderListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in order list panel changed to : '" + newValue + "'"); + raise(new OrderPanelSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code OrderCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + orderListView.scrollTo(index); + orderListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToOrderListRequestEvent(JumpToOrderListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + @Subscribe + private void handleDeliverymanPanelSelectionChangedEvent(DeliveryManPanelSelectionChangedEvent event) { + orderListView.getSelectionModel().clearSelection(); + } + + @Subscribe + private void handleBackToHomeRequest(BackToHomeEvent event) { + orderListView.getSelectionModel().clearSelection(); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Order} using a {@code OrderCard}. + */ + class OrderListViewCell extends ListCell { + @Override + protected void updateItem(Order order, boolean empty) { + super.updateItem(order, empty); + + if (empty || order == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new OrderCard(order, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index f6727ea83abd..000000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,70 +0,0 @@ -package seedu.address.ui; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.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 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - 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().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} 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 80080adb4305..000000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,83 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -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.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -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); - setConnections(personList); - registerAsAnEventHandler(this); - } - - private void setConnections(ObservableList personList) { - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - setEventHandlerForSelectionChangeEvent(); - } - - private void setEventHandlerForSelectionChangeEvent() { - personListView.getSelectionModel().selectedItemProperty() - .addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - logger.fine("Selection in person list panel changed to : '" + newValue + "'"); - raise(new PersonPanelSelectionChangedEvent(newValue)); - } - }); - } - - /** - * Scrolls to the {@code PersonCard} at the {@code index} and selects it. - */ - private void scrollTo(int index) { - Platform.runLater(() -> { - personListView.scrollTo(index); - personListView.getSelectionModel().clearAndSelect(index); - }); - } - - @Subscribe - private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - scrollTo(event.targetIndex); - } - - /** - * 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/StatisticsPanel.java b/src/main/java/seedu/address/ui/StatisticsPanel.java new file mode 100644 index 000000000000..2c401c0f4511 --- /dev/null +++ b/src/main/java/seedu/address/ui/StatisticsPanel.java @@ -0,0 +1,106 @@ +package seedu.address.ui; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.Stack; +import java.util.TreeMap; +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.chart.BarChart; +import javafx.scene.chart.CategoryAxis; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; + +/** + * Panel containing the statistics of orders. + */ +public class StatisticsPanel extends UiPart { + + private static final String FXML = "Statistics.fxml"; + private final Logger logger = LogsCenter.getLogger(StatisticsPanel.class); + + @FXML + private CategoryAxis xAxis; + + @FXML + private NumberAxis yAxis; + + @FXML + private BarChart barChart; + + @FXML + private Label orderCount; + + @FXML + private ProgressBar orderProgress; + + @FXML + private Label trendingFood; + + public StatisticsPanel() { + super(FXML); + } + + /** + * Updates the bar graph in the component with orders over the past 7 days. It does this by calling + * the pollLastEntry method from a balanced binary search tree + * + * @param historyDate TreeMap of order count arranged by their day/month + */ + public void initialize(TreeMap historyDate) { + xAxis.setLabel("Date"); + yAxis.setLabel("Order Count"); + barChart.setAnimated(false); + barChart.setLegendVisible(false); + + TreeMap history = new TreeMap<>(historyDate); + + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM"); + + Stack> storage = new Stack<>(); + for (int i = 0; i < 7; ++i) { + + if (history.isEmpty()) { + break; + } + + Map.Entry entry = history.pollLastEntry(); + storage.push(entry); + } + + XYChart.Series series1 = new XYChart.Series<>(); + while (!storage.empty()) { + Map.Entry entry = storage.pop(); + series1.getData().add(new XYChart.Data<>(dateFormat.format(entry.getKey()), entry.getValue())); + } + + barChart.getData().clear(); + barChart.getData().add(series1); + } + + /** + * Updates the labels in the statistics panel based on information given + * + * @param count Total count of orders + * @param progress Percentage of orders that are pending + * @param food The most popular food item + */ + public void updateLabels(int count, double progress, String food) { + + logger.info("PROGRESS " + progress); + + orderCount.setText("Total Orders: " + Integer.toString(count)); + orderProgress.setProgress(progress); + trendingFood.setText("Trending: " + food); + } + + public void clear() { + barChart.getData().clear(); + } +} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index f6ba29502422..11e0333ac834 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -14,7 +14,9 @@ import javafx.fxml.FXML; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.FoodZoomChangedEvent; +import seedu.address.commons.events.model.UserLoggedInEvent; +import seedu.address.commons.events.model.UserLoggedOutEvent; /** * A ui for the status bar that is displayed at the footer of the application. @@ -23,7 +25,11 @@ public class StatusBarFooter extends UiPart { public static final String SYNC_STATUS_INITIAL = "Not updated yet in this session"; public static final String SYNC_STATUS_UPDATED = "Last Updated: %s"; - + public static final String MESSAGE_LOGGED_IN_AS = "Logged in as : "; + public static final String MESSAGE_LOGGED_OUT = "Not logged in."; + public static final String MESSAGE_UPDATING_STATUS_BAR = "Updating status bar."; + private static final Logger logger = LogsCenter.getLogger(StatusBarFooter.class); + private static final String FXML = "StatusBarFooter.fxml"; /** * Used to generate time stamps. * @@ -33,15 +39,12 @@ public class StatusBarFooter extends UiPart { * but it should be easier once we have factories/DI frameworks. */ private static Clock clock = Clock.systemDefaultZone(); - - private static final Logger logger = LogsCenter.getLogger(StatusBarFooter.class); - - private static final String FXML = "StatusBarFooter.fxml"; - @FXML private StatusBar syncStatus; @FXML private StatusBar saveLocationStatus; + @FXML + private StatusBar usernameText; public StatusBarFooter(Path saveLocation) { @@ -49,20 +52,21 @@ public StatusBarFooter(Path saveLocation) { setSyncStatus(SYNC_STATUS_INITIAL); setSaveLocation(Paths.get(".").resolve(saveLocation).toString()); registerAsAnEventHandler(this); + setUsername(MESSAGE_LOGGED_OUT); } /** - * Sets the clock used to determine the current time. + * Returns the clock currently in use. */ - public static void setClock(Clock clock) { - StatusBarFooter.clock = clock; + public static Clock getClock() { + return clock; } /** - * Returns the clock currently in use. + * Sets the clock used to determine the current time. */ - public static Clock getClock() { - return clock; + public static void setClock(Clock clock) { + StatusBarFooter.clock = clock; } private void setSaveLocation(String location) { @@ -73,11 +77,27 @@ private void setSyncStatus(String status) { Platform.runLater(() -> syncStatus.setText(status)); } + private void setUsername(String username) { + Platform.runLater(() -> usernameText.setText(username)); + } + @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { + public void handleFoodZoomChangedEvent(FoodZoomChangedEvent abce) { long now = clock.millis(); String lastUpdated = new Date(now).toString(); logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); setSyncStatus(String.format(SYNC_STATUS_UPDATED, lastUpdated)); } + + @Subscribe + private void handleUserLoggedInEvent(UserLoggedInEvent event) { + logger.info(MESSAGE_UPDATING_STATUS_BAR); + setUsername(MESSAGE_LOGGED_IN_AS + event.data.getUsername().toString()); + } + + @Subscribe + private void handleUserLogoutEvent(UserLoggedOutEvent event) { + logger.info(MESSAGE_UPDATING_STATUS_BAR); + setUsername(MESSAGE_LOGGED_OUT); + } } diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/address/ui/Ui.java index e6a67fe8c027..c97d61a72f9e 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/address/ui/Ui.java @@ -7,10 +7,14 @@ */ public interface Ui { - /** Starts the UI (and the App). */ + /** + * Starts the UI (and the App). + */ void start(Stage primaryStage); - /** Stops the UI. */ + /** + * Stops the UI. + */ void stop(); } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 3fd3c17be156..4de6889fa340 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -30,7 +30,7 @@ public class UiManager extends ComponentManager implements Ui { public static final String FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE = "Could not save data to file"; 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/scooter.png"; private Logic logic; private Config config; @@ -44,6 +44,26 @@ public UiManager(Logic logic, Config config, UserPrefs prefs) { this.prefs = prefs; } + /** + * Shows an alert dialog on {@code owner} with the given parameters. + * This method only returns after the user has closed the alert dialog. + */ + private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, + String contentText) { + final Alert alert = new Alert(type); + alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); + alert.initOwner(owner); + alert.setTitle(title); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.getDialogPane().setId(ALERT_DIALOG_PANE_FIELD_ID); + alert.showAndWait(); + } + + void showAlertDialogAndWait(Alert.AlertType type, String title, String headerText, String contentText) { + showAlertDialogAndWait(mainWindow.getPrimaryStage(), type, title, headerText, contentText); + } + @Override public void start(Stage primaryStage) { logger.info("Starting UI..."); @@ -66,7 +86,6 @@ public void start(Stage primaryStage) { public void stop() { prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); mainWindow.hide(); - mainWindow.releaseResources(); } private void showFileOperationAlertAndWait(String description, String details, Throwable cause) { @@ -78,26 +97,6 @@ private Image getImage(String imagePath) { return new Image(MainApp.class.getResourceAsStream(imagePath)); } - void showAlertDialogAndWait(Alert.AlertType type, String title, String headerText, String contentText) { - showAlertDialogAndWait(mainWindow.getPrimaryStage(), type, title, headerText, contentText); - } - - /** - * Shows an alert dialog on {@code owner} with the given parameters. - * This method only returns after the user has closed the alert dialog. - */ - private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, - String contentText) { - final Alert alert = new Alert(type); - alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); - alert.initOwner(owner); - alert.setTitle(title); - alert.setHeaderText(headerText); - alert.setContentText(contentText); - alert.getDialogPane().setId(ALERT_DIALOG_PANE_FIELD_ID); - alert.showAndWait(); - } - /** * Shows an error alert dialog with {@code title} and error message, {@code e}, * and exits the application after the user has closed the alert dialog. diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/address/ui/UiPart.java index 5c237e57154b..2e83c35625e1 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/address/ui/UiPart.java @@ -16,7 +16,9 @@ */ public abstract class UiPart { - /** Resource folder where FXML files are stored. */ + /** + * Resource folder where FXML files are stored. + */ public static final String FXML_FILE_FOLDER = "/view/"; private final FXMLLoader fxmlLoader = new FXMLLoader(); @@ -31,6 +33,7 @@ public UiPart(URL fxmlFileUrl) { /** * Constructs a UiPart using the specified FXML file within {@link #FXML_FILE_FOLDER}. + * * @see #UiPart(URL) */ public UiPart(String fxmlFileName) { @@ -47,12 +50,23 @@ public UiPart(URL fxmlFileUrl, T root) { /** * Constructs a UiPart with the specified FXML file within {@link #FXML_FILE_FOLDER} and root object. + * * @see #UiPart(URL, T) */ public UiPart(String fxmlFileName, T root) { this(getFxmlFileUrl(fxmlFileName), root); } + /** + * Returns the FXML file URL for the specified FXML file name within {@link #FXML_FILE_FOLDER}. + */ + private static URL getFxmlFileUrl(String fxmlFileName) { + requireNonNull(fxmlFileName); + String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; + URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); + return requireNonNull(fxmlFileUrl); + } + /** * Returns the root object of the scene graph of this UiPart. */ @@ -62,6 +76,7 @@ public T getRoot() { /** * Raises the event via {@link EventsCenter#post(BaseEvent)} + * * @param event */ protected void raise(BaseEvent event) { @@ -70,6 +85,7 @@ protected void raise(BaseEvent event) { /** * Registers the object as an event handler at the {@link EventsCenter} + * * @param handler usually {@code this} */ protected void registerAsAnEventHandler(Object handler) { @@ -78,8 +94,9 @@ protected void registerAsAnEventHandler(Object handler) { /** * Loads the object hierarchy from a FXML document. + * * @param location Location of the FXML document. - * @param root Specifies the root of the object hierarchy. + * @param root Specifies the root of the object hierarchy. */ private void loadFxmlFile(URL location, T root) { requireNonNull(location); @@ -93,14 +110,4 @@ private void loadFxmlFile(URL location, T root) { } } - /** - * Returns the FXML file URL for the specified FXML file name within {@link #FXML_FILE_FOLDER}. - */ - private static URL getFxmlFileUrl(String fxmlFileName) { - requireNonNull(fxmlFileName); - String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; - URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); - return requireNonNull(fxmlFileUrl); - } - } diff --git a/src/main/java/seedu/address/ui/display/DeliverymanDisplayCard.java b/src/main/java/seedu/address/ui/display/DeliverymanDisplayCard.java new file mode 100644 index 000000000000..0170ae822507 --- /dev/null +++ b/src/main/java/seedu/address/ui/display/DeliverymanDisplayCard.java @@ -0,0 +1,76 @@ +package seedu.address.ui.display; + +import java.util.Set; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.order.Order; +import seedu.address.ui.UiPart; + +/** + * UI Component that displays relevant information of a deliveryman. + */ +public class DeliverymanDisplayCard extends UiPart { + private static final String FXML = "display/DeliverymanDisplayCard.fxml"; + private static final String BUSY_LABEL_CLASS = "busy"; + private static final String AVAILABLE_LABEL_CLASS = "available"; + + public final Deliveryman deliveryman; + + @FXML + private Label namePrintOut; + @FXML + private Label deliverymanStatusPrintOut; + @FXML + private VBox orderListDisplay; + + + public DeliverymanDisplayCard(Deliveryman deliveryman) { + super(FXML); + this.deliveryman = deliveryman; + namePrintOut.setText(deliveryman.getName().fullName); + + setDeliverymanStatus(); + setOrders(deliveryman.getOrders()); + } + + private void setDeliverymanStatus() { + if (deliveryman.getOrders().size() > 0) { + deliverymanStatusPrintOut.setText("Delivering"); + deliverymanStatusPrintOut.getStyleClass().clear(); + deliverymanStatusPrintOut.getStyleClass().add(BUSY_LABEL_CLASS); + } else { + deliverymanStatusPrintOut.setText("Available"); + deliverymanStatusPrintOut.getStyleClass().clear(); + deliverymanStatusPrintOut.getStyleClass().add(AVAILABLE_LABEL_CLASS); + } + } + + private void setOrders(Set orders) { + for (Order o : orders) { + + OrderPrintOut orderPrintOut = new OrderPrintOut(o); + orderListDisplay.getChildren().add(orderPrintOut.getRoot()); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeliverymanDisplayCard)) { + return false; + } + + // state check + DeliverymanDisplayCard card = (DeliverymanDisplayCard) other; + return deliveryman.equals(card.deliveryman); + } +} diff --git a/src/main/java/seedu/address/ui/display/OrderDisplayCard.java b/src/main/java/seedu/address/ui/display/OrderDisplayCard.java new file mode 100644 index 000000000000..280ca0e9e13c --- /dev/null +++ b/src/main/java/seedu/address/ui/display/OrderDisplayCard.java @@ -0,0 +1,67 @@ +package seedu.address.ui.display; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import javafx.scene.text.Text; +import seedu.address.model.deliveryman.Deliveryman; +import seedu.address.model.order.Order; +import seedu.address.ui.UiPart; + +/** + * UI Component that represents the display for an Order + */ +public class OrderDisplayCard extends UiPart { + private static final String FXML = "display/OrderDisplayCard.fxml"; + + public final Order order; + + @FXML + private Label foodL; + @FXML + private Label dateL; + @FXML + private Label phoneL; + @FXML + private Text addressL; + @FXML + private Label nameL; + @FXML + private Label deliverymanL; + + public OrderDisplayCard(Order order) { + super(FXML); + this.order = order; + + nameL.setText("Name: " + order.getName().fullName); + dateL.setText("Date: " + order.getDate().toString()); + foodL.setText("Food: " + order.getFood().toString()); + phoneL.setText("Phone: " + order.getPhone().toString()); + addressL.setText("Address: " + order.getAddress().toString()); + deliverymanL.setText("Deliveryman: " + getFullNameOrNull(order.getDeliveryman())); + } + + private String getFullNameOrNull(Deliveryman deliveryman) { + if (deliveryman == null) { + return "Not assigned."; + } + return deliveryman.getName().fullName; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OrderDisplayCard)) { + return false; + } + + // state check + OrderDisplayCard card = (OrderDisplayCard) other; + return order.equals(card.order); + } +} diff --git a/src/main/java/seedu/address/ui/display/OrderDisplayListCard.java b/src/main/java/seedu/address/ui/display/OrderDisplayListCard.java new file mode 100644 index 000000000000..2ce7e526f8bc --- /dev/null +++ b/src/main/java/seedu/address/ui/display/OrderDisplayListCard.java @@ -0,0 +1,55 @@ +package seedu.address.ui.display; + +import java.util.stream.Collectors; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.order.Food; +import seedu.address.model.order.Order; +import seedu.address.ui.UiPart; + +/** + * UI Component representing the display of a single order in a list. + */ +public class OrderDisplayListCard extends UiPart { + private static final String FXML = "display/OrderDisplayListCard.fxml"; + private static final String NAME_LABEL_FORMAT = "Contact: %1$s (%2$s)"; + private static final String FOOD_LABEL_FORMAT = "Order: %s"; + + public final Order order; + + @FXML + private Label address; + @FXML + private Label name; + @FXML + private Label food; + + public OrderDisplayListCard(Order order) { + super(FXML); + this.order = order; + address.setText(order.getAddress().toString()); + name.setText(String.format(NAME_LABEL_FORMAT, order.getName().fullName, order.getPhone().toString())); + + food.setText(String.format(FOOD_LABEL_FORMAT, String.join(", ", + order.getFood().stream().map(Food::toString).collect(Collectors.toSet())))); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OrderDisplayListCard)) { + return false; + } + + // state check + OrderDisplayListCard card = (OrderDisplayListCard) other; + return order.equals(card.order); + } +} diff --git a/src/main/java/seedu/address/ui/display/OrderPrintOut.java b/src/main/java/seedu/address/ui/display/OrderPrintOut.java new file mode 100644 index 000000000000..8942a082fedd --- /dev/null +++ b/src/main/java/seedu/address/ui/display/OrderPrintOut.java @@ -0,0 +1,63 @@ +package seedu.address.ui.display; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import javafx.scene.text.Text; +import seedu.address.model.order.Order; +import seedu.address.ui.UiPart; + +/** + * UI Component representing the display of a single order in the deliveryman's list of orders. + */ +public class OrderPrintOut extends UiPart { + + private static final String FXML = "display/OrderPrintOut.fxml"; + + private final Order order; + + @FXML + private Label nameP; + + @FXML + private Label dateP; + + @FXML + private Text addressP; + + @FXML + private Label phoneP; + + public OrderPrintOut(Order order) { + super(FXML); + this.order = order; + + nameP.setText("Order from " + order.getName().fullName); + dateP.setText(order.getDate().toString()); + phoneP.setText(order.getPhone().toString()); + addressP.setText(order.getAddress().toString()); + } + + + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OrderPrintOut)) { + return false; + } + + // state check + OrderPrintOut card = (OrderPrintOut) other; + return order.equals(card.order); + } + +} + + + diff --git a/src/main/java/seedu/address/ui/util/MapDataUtil.java b/src/main/java/seedu/address/ui/util/MapDataUtil.java new file mode 100644 index 000000000000..f2ddbaf76993 --- /dev/null +++ b/src/main/java/seedu/address/ui/util/MapDataUtil.java @@ -0,0 +1,153 @@ +package seedu.address.ui.util; + +import java.util.Map; + +/** + * A cache that maps postal codes to districts on the Singapore map + */ +public class MapDataUtil { + + public static final Map POSTAL_CODE_CACHE = Map.ofEntries( + Map.entry("01", 1), + Map.entry("02", 1), + Map.entry("03", 1), + Map.entry("04", 1), + Map.entry("05", 1), + Map.entry("06", 1), + + Map.entry("07", 2), + Map.entry("08", 2), + + Map.entry("14", 3), + Map.entry("15", 3), + Map.entry("16", 3), + + Map.entry("09", 4), + Map.entry("10", 4), + + Map.entry("11", 5), + Map.entry("12", 5), + Map.entry("13", 5), + + Map.entry("17", 6), + + Map.entry("18", 7), + Map.entry("19", 7), + + Map.entry("20", 8), + Map.entry("21", 8), + + Map.entry("22", 9), + Map.entry("23", 9), + + Map.entry("24", 10), + Map.entry("25", 10), + Map.entry("26", 10), + Map.entry("27", 10), + + Map.entry("28", 11), + Map.entry("29", 11), + Map.entry("30", 11), + + Map.entry("31", 12), + Map.entry("32", 12), + Map.entry("33", 12), + + Map.entry("34", 13), + Map.entry("35", 13), + Map.entry("36", 13), + Map.entry("37", 13), + + Map.entry("38", 14), + Map.entry("39", 14), + Map.entry("40", 14), + Map.entry("41", 14), + + Map.entry("42", 15), + Map.entry("43", 15), + Map.entry("44", 15), + Map.entry("45", 15), + + Map.entry("46", 16), + Map.entry("47", 16), + Map.entry("48", 16), + + Map.entry("49", 17), + Map.entry("50", 17), + Map.entry("81", 17), + + Map.entry("51", 18), + Map.entry("52", 18), + + Map.entry("53", 19), + Map.entry("54", 19), + Map.entry("55", 19), + Map.entry("82", 19), + + Map.entry("56", 20), + Map.entry("57", 20), + + Map.entry("58", 21), + Map.entry("59", 21), + + Map.entry("60", 22), + Map.entry("61", 22), + Map.entry("62", 22), + Map.entry("63", 22), + Map.entry("64", 22), + + Map.entry("65", 23), + Map.entry("66", 23), + Map.entry("67", 23), + Map.entry("68", 23), + + Map.entry("69", 24), + Map.entry("70", 24), + Map.entry("71", 24), + + Map.entry("72", 25), + Map.entry("73", 25), + + Map.entry("77", 26), + Map.entry("78", 26), + + Map.entry("75", 27), + Map.entry("76", 27), + + Map.entry("79", 28), + Map.entry("80", 28) + ); + + public static final Map DISTRICT_CACHE = Map.ofEntries( + Map.entry(1, new Integer[]{8, 5}), + Map.entry(2, new Integer[]{9, 5}), + Map.entry(3, new Integer[]{8, 4}), + Map.entry(4, new Integer[]{9, 4}), + Map.entry(5, new Integer[]{7, 3}), + Map.entry(6, new Integer[]{8, 5}), + Map.entry(7, new Integer[]{8, 5}), + Map.entry(8, new Integer[]{7, 5}), + Map.entry(9, new Integer[]{8, 4}), + Map.entry(10, new Integer[]{7, 4}), + Map.entry(11, new Integer[]{6, 4}), + Map.entry(12, new Integer[]{5, 5}), + Map.entry(13, new Integer[]{5, 5}), + Map.entry(14, new Integer[]{6, 6}), + Map.entry(15, new Integer[]{7, 6}), + Map.entry(16, new Integer[]{6, 7}), + Map.entry(17, new Integer[]{5, 8}), + Map.entry(18, new Integer[]{4, 7}), + Map.entry(19, new Integer[]{4, 6}), + Map.entry(20, new Integer[]{4, 4}), + Map.entry(21, new Integer[]{5, 3}), + Map.entry(22, new Integer[]{6, 2}), + Map.entry(23, new Integer[]{4, 3}), + Map.entry(24, new Integer[]{3, 2}), + Map.entry(25, new Integer[]{2, 3}), + Map.entry(26, new Integer[]{3, 4}), + Map.entry(27, new Integer[]{1, 4}), + Map.entry(28, new Integer[]{3, 5}) + ); + + +} diff --git a/src/main/resources/images/bag.png b/src/main/resources/images/bag.png new file mode 100644 index 000000000000..7918fa0e18af Binary files /dev/null and b/src/main/resources/images/bag.png differ diff --git a/src/main/resources/images/cutlery.png b/src/main/resources/images/cutlery.png new file mode 100644 index 000000000000..6e6a954ce773 Binary files /dev/null and b/src/main/resources/images/cutlery.png differ diff --git a/src/main/resources/images/deliveryboy.png b/src/main/resources/images/deliveryboy.png new file mode 100644 index 000000000000..38e89f3c600d Binary files /dev/null and b/src/main/resources/images/deliveryboy.png differ diff --git a/src/main/resources/images/famous.png b/src/main/resources/images/famous.png new file mode 100644 index 000000000000..28fe4c1efa74 Binary files /dev/null and b/src/main/resources/images/famous.png differ diff --git a/src/main/resources/images/order_placeholder.png b/src/main/resources/images/order_placeholder.png new file mode 100644 index 000000000000..fe3afa7b9cb6 Binary files /dev/null and b/src/main/resources/images/order_placeholder.png differ diff --git a/src/main/resources/images/placeholder.png b/src/main/resources/images/placeholder.png new file mode 100644 index 000000000000..9cf6dcaf4fa4 Binary files /dev/null and b/src/main/resources/images/placeholder.png differ diff --git a/src/main/resources/images/receipt.png b/src/main/resources/images/receipt.png new file mode 100644 index 000000000000..7365a37c3f30 Binary files /dev/null and b/src/main/resources/images/receipt.png differ diff --git a/src/main/resources/images/red_placeholder.png b/src/main/resources/images/red_placeholder.png new file mode 100644 index 000000000000..5661b305a435 Binary files /dev/null and b/src/main/resources/images/red_placeholder.png differ diff --git a/src/main/resources/images/scooter.png b/src/main/resources/images/scooter.png new file mode 100644 index 000000000000..86f53a30a693 Binary files /dev/null and b/src/main/resources/images/scooter.png differ diff --git a/src/main/resources/images/sgmap.png b/src/main/resources/images/sgmap.png new file mode 100644 index 000000000000..9e642583f254 Binary files /dev/null and b/src/main/resources/images/sgmap.png differ diff --git a/src/main/resources/images/shipping.png b/src/main/resources/images/shipping.png new file mode 100644 index 000000000000..a5078f141b4a Binary files /dev/null and b/src/main/resources/images/shipping.png differ diff --git a/src/main/resources/view/BrowserPanel.fxml b/src/main/resources/view/BrowserPanel.fxml index 31670827e3da..4ae84568b38a 100644 --- a/src/main/resources/view/BrowserPanel.fxml +++ b/src/main/resources/view/BrowserPanel.fxml @@ -2,7 +2,6 @@ - - + diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 70bd59ab3215..ba3642b52ce7 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -1,9 +1,20 @@ + + + - - - - + +
+ + + + + +
+ + +
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index c8941ea18263..e09e811ab87d 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -56,11 +56,7 @@ -fx-size: 35; -fx-border-width: 0 0 1 0; -fx-background-color: transparent; - -fx-border-color: - transparent - transparent - derive(-fx-base, 80%) - transparent; + -fx-border-color: transparent transparent derive(-fx-base, 80%) transparent; -fx-border-insets: 0 10 1 0; } @@ -95,7 +91,7 @@ .list-cell { -fx-label-padding: 0 0 0 0; - -fx-graphic-text-gap : 0; + -fx-graphic-text-gap: 0; -fx-padding: 0 0 0 0; } @@ -133,13 +129,13 @@ } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#1d1d1d, 20%); } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); - -fx-border-top-width: 1px; + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: derive(#1d1d1d, 10%); + -fx-border-top-width: 1px; } .status-bar { @@ -228,8 +224,8 @@ } .button:pressed, .button:default:hover:pressed { - -fx-background-color: white; - -fx-text-fill: #1d1d1d; + -fx-background-color: white; + -fx-text-fill: #1d1d1d; } .button:focused { diff --git a/src/main/resources/view/Dashboard.fxml b/src/main/resources/view/Dashboard.fxml new file mode 100644 index 000000000000..6bf906160646 --- /dev/null +++ b/src/main/resources/view/Dashboard.fxml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/DeliverymanListCard.fxml b/src/main/resources/view/DeliverymanListCard.fxml new file mode 100644 index 000000000000..aa8f233a4c1e --- /dev/null +++ b/src/main/resources/view/DeliverymanListCard.fxml @@ -0,0 +1,23 @@ + + + + + + + + + +
+ + + + + +
+ + + + +
diff --git a/src/main/resources/view/DeliverymanListPanel.fxml b/src/main/resources/view/DeliverymanListPanel.fxml new file mode 100644 index 000000000000..ab615a10f328 --- /dev/null +++ b/src/main/resources/view/DeliverymanListPanel.fxml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/view/Display.fxml b/src/main/resources/view/Display.fxml new file mode 100644 index 000000000000..19789c008d99 --- /dev/null +++ b/src/main/resources/view/Display.fxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964d..b3260de669a8 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -18,3 +18,29 @@ .tooltip-text { -fx-text-fill: white; } + +#orderListViewForCard { + -fx-background-color: transparent; +} + +#orderListViewForCard .list-cell { + -fx-background-color: transparent; +} + +#orderListViewForCard .list-cell:filled:even { + -fx-background-color: #e5ffde; +} + +#orderListViewForCard .list-cell:filled:odd { + -fx-background-color: derive(#e5ffde, 30%); +} + +#orderListViewForCard .label { + -fx-text-fill: #363636; +} + +#orderListViewForCard #address { + -fx-font-weight: bold; + -fx-font-size: 11; +} + diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index c07e8e685014..9c60c3ded02e 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -1,18 +1,18 @@ - + + - - - + - - - - - - - - + + + + + + + + diff --git a/src/main/resources/view/LoginPanel.fxml b/src/main/resources/view/LoginPanel.fxml new file mode 100644 index 000000000000..a1351963d57a --- /dev/null +++ b/src/main/resources/view/LoginPanel.fxml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index daf386d8f5b8..068e9b806c0a 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -2,67 +2,55 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/view/MapCard.fxml b/src/main/resources/view/MapCard.fxml new file mode 100644 index 000000000000..60e08199e6e6 --- /dev/null +++ b/src/main/resources/view/MapCard.fxml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/src/main/resources/view/MapPanel.fxml b/src/main/resources/view/MapPanel.fxml new file mode 100644 index 000000000000..5a9a3a03e999 --- /dev/null +++ b/src/main/resources/view/MapPanel.fxml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/OrderListCard.fxml b/src/main/resources/view/OrderListCard.fxml new file mode 100644 index 000000000000..ae50318d6030 --- /dev/null +++ b/src/main/resources/view/OrderListCard.fxml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/OrderListPanel.fxml b/src/main/resources/view/OrderListPanel.fxml new file mode 100644 index 000000000000..c1262abc1af6 --- /dev/null +++ b/src/main/resources/view/OrderListPanel.fxml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad558..bfba9e3969aa 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -8,29 +8,28 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml index 8836d323cc5d..c45fcfc7d68f 100644 --- a/src/main/resources/view/PersonListPanel.fxml +++ b/src/main/resources/view/PersonListPanel.fxml @@ -2,7 +2,6 @@ - - - + + diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index 58d5ad3dc56c..ec006c4c173f 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -2,8 +2,7 @@ - - -