diff --git a/.gitignore b/.gitignore index 823d175eb670..060715bc367b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,12 +9,21 @@ lib/* *.log.* *.csv config.json -src/test/data/sandbox/ +passwords.json preferences.json +src/test/data/sandbox/ .DS_Store ./screenshot*.png classes/ -/data/ /bin/ src/main/resources/docs/ out/ +data/ +commands_log/* +src/test/data/XmlUtilTest/validConcierge.xml +src/test/data/XmlUtilTest/validGuest.xml +src/test/data/XmlUtilTest/validRoom.xml +src/test/data/XmlUtilTest/invalidGuest.xml +src/test/data/XmlUtilTest/invalidRoom.xml +src/test/data/XmlUtilTest/invalidRoomField.xml +src/test/data/XmlSerializableConciergeTest/typicalConcierge.xml diff --git a/LICENSE b/LICENSE index 39b3478982c3..e3422d98ce0b 100644 --- a/LICENSE +++ b/LICENSE @@ -6,7 +6,7 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is +copies of the Software, and to permit guests to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all diff --git a/README.adoc b/README.adoc index 450054624f48..cd3c91d6d83e 100644 --- a/README.adoc +++ b/README.adoc @@ -1,33 +1,32 @@ -= Address Book (Level 4) += Concierge(TM) v1.4 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://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://travis-ci.org/CS2103-AY1819S1-F11-2/main[image:https://travis-ci.org/CS2103-AY1819S1-F11-2/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/CS2103-AY1819S1-F11-2/main[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] +https://coveralls.io/github/CS2103-AY1819S1-F11-2/main?branch=master[image:https://coveralls.io/repos/github/CS2103-AY1819S1-F11-2/main/badge.svg?branch=master[Coverage Status]] +image:https://api.codacy.com/project/badge/Grade/e246831fbe73495b83cc53e409ff7fb1["Codacy code quality", link="https://www.codacy.com/app/adamwth/main?utm_source=github.com&utm_medium=referral&utm_content=CS2103-AY1819S1-F11-2/main&utm_campaign=Badge_Grade"] ifdef::env-github[] image::docs/images/Ui.png[width="600"] +image::docs/images/ConciergeFinal.png[width="600"] endif::[] ifndef::env-github[] image::images/Ui.png[width="600"] +image::images/ConciergeFinal.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. +* Concierge(TM) is a desktop Hotel Management application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* It is a concise Java application intended for small hotels looking for a simple application to manage basic hotel features. +* Value Proposition +** A simple, intuitive and free command line tool for managing a small-scale hotel (~ 100 rooms). +** Hassle-free for receptionists in managing day-to-day hotel operations. +** Protects against error-prone situations in the context of managing hotel bookings. == Site Map * <> * <> -* <> * <> * <> @@ -36,5 +35,6 @@ 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] +* The original source of the code is AddressBook - level 4 : https://github.com/se-edu/ == Licence : link:LICENSE[MIT] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..5d42f33dbeb7 --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,30 @@ +{ + "authors": + [ + { + "githubId": "adamwth", + "displayName": "CHEW ... SOON", + "authorNames": ["adamwth", "Adam"] + }, + { + "githubId": "pikulet", + "displayName": "JOYCE...HUHUI", + "authorNames": ["pikulet", "Joyce"] + }, + { + "githubId": "neilish3re", + "displayName": "NEIL ...MEHTA", + "authorNames": ["neilish3re", "Neil Mehta", "Neil"] + }, + { + "githubId": "JiaqingTan", + "displayName": "TAN J...AQING", + "authorNames": ["JiaqingTan"] + }, + { + "githubId": "teowz46", + "displayName": "TEO W...ZHENG", + "authorNames": ["teowz46"] + } + ] +} diff --git a/build.gradle b/build.gradle index f8e614f8b49b..514c31909c79 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'concierge.jar' destinationDir = file("${buildDir}/jar/") } @@ -134,6 +134,7 @@ test { testLogging { events TestLogEvent.FAILED, TestLogEvent.SKIPPED + exceptionFormat 'full' // Prints the currently running test's name in the CI's build log, // so that we can check if tests are being silently skipped or @@ -207,9 +208,8 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level4', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level4', - 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) + 'site-name': 'Concierge', + 'site-githuburl': 'https://github.com/CS2103-AY1819S1-F11-2/main', ] options['template_dirs'].each { diff --git a/config.json b/config.json new file mode 100644 index 000000000000..5323b9c79b3e --- /dev/null +++ b/config.json @@ -0,0 +1,5 @@ +{ + "appTitle" : "Concierge", + "logLevel" : "INFO", + "userPrefsFilePath" : "preferences.json" +} diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index e647ed1e715a..f97b4cb96c5c 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,59 @@ :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.}_ + +Concierge(TM) was developed by the CS2103 F11-2 team. + {empty} + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== Chew Yong Soon +image::adamwth.png[width="150", align="left"] +{empty}[https://github.com/adamwth[github]] [<>] -Role: Project Advisor +Role: Team Lead + +Responsibilities: UI Design, Model Component, JavaFX expert + +Side-responsiblities: Scheduling and tracking, Integration ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Joyce Yeo Shuhui +image::pikulet.png[width="150", align="left"] +{empty}[http://github.com/pikulet[github]] [<>] -Role: Team Lead + -Responsibilities: UI +Role: Developer + +Responsibilities: Documentation, Storage Component, IntelliJ expert + +Side-responsiblities: Deliverables and deadlines ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Teo Wei Zheng +image::teowz46.png[width="150", align="left"] +{empty}[http://github.com/teowz46[github]] [<>] Role: Developer + -Responsibilities: Data +Responsibilities: Logic Component, Travis CI expert + +Side-responsiblities: Deliverables and deadlines ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Tan Jia Qing +image::jiaqingtan.png[width="150", align="left"] +{empty}[http://github.com/JiaqingTan[github]] [<>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: UI Design & Implementation, UI Component, JavaFX expert + +Side-responsiblities: Code quality ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Neil Mehta +image::neilish3re.png[width="150", align="left"] +{empty}[http://github.com/neilish3re[github]] [<>] Role: Developer + -Responsibilities: UI +Responsibilities: Testing, Commons Component, JUnit expert + +Side-responsiblities: Git expert + ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 5de5363abffd..53eeef6f6d70 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-F11-2/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach our Team Lead at `yschew@u.nus.edu`. + diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index ea58481e4740..e60da913ff6b 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += Concierge(TM) - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -12,9 +12,9 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103-AY1819S1-F11-2/main -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Team Concierge(TM)`      Since: `Aug 2018`      Licence: `MIT` == Setting up @@ -100,6 +100,8 @@ When you are ready to start coding, == Design +[NOTE] The diagrams in this section are intended to be updated only in v.1.5. + [[Design-Architecture]] === Architecture @@ -147,7 +149,7 @@ The _Sequence Diagram_ below shows how the components interact for the scenario image::SDforDeletePerson.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 `ConciergeChangedEvent` when Concierge 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. @@ -182,14 +184,14 @@ The `UI` component, [[fig-LogicClassDiagram]] .Structure of the Logic Component -image::LogicClassDiagram.png[width="800"] +image::NewLogicComponentClassDiagram.png[width="960"] *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 `ConciergeParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. +. The command execution can affect the `Model` (e.g. adding a guest) 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. @@ -208,12 +210,12 @@ 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 Concierge 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. * does not depend on any of the other three components. [NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + +As a more OOP model, we can store a `Tag` list in `Concierge`, which `Guest` can reference. This would allow `Concierge` to only require one `Tag` object per unique `Tag`, instead of each `Guest` needing their own `Tag` object. An example of how such a model may look like is given below. Only UniqueGuestList is shown for simplicity. + + image:ModelClassBetterOopDiagram.png[width="800"] @@ -228,7 +230,7 @@ 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 Concierge data in xml format and read it back. [[Design-Commons]] === Common classes @@ -241,56 +243,59 @@ This section describes some noteworthy details on how certain features are imple // tag::undoredo[] === Undo/Redo feature + +[NOTE] +Diagrammatic references to "AddressBook" are intended to be removed in v.1.5. ==== 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`. +The undo/redo mechanism is facilitated by `VersionedConcierge`. +It extends `Concierge` with an undo/redo history, stored internally as an `conciergeStateList` and `currentStatePointer`. Additionally, it implements the following operations: -* `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. +* `VersionedConcierge#commit()` -- Saves the current Concierge state in its history. +* `VersionedConcierge#undo()` -- Restores the previous Concierge state from its history. +* `VersionedConcierge#redo()` -- Restores a previously undone Concierge state from its history. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +These operations are exposed in the `Model` interface as `Model#commitConcierge()`, `Model#undoConcierge()` and `Model#redoConcierge()` respectively. Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -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. +Step 1. The user launches the application for the first time. The `VersionedConcierge` will be initialized with the initial Concierge state, and the `currentStatePointer` pointing to that single Concierge state. image::UndoRedoStartingStateListDiagram.png[width="800"] -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. +Step 2. The user executes `delete 5` command to delete the 5th guest in Concierge. The `delete` command calls `Model#commitConcierge()`, causing the modified state of Concierge after the `delete 5` command executes to be saved in the `conciergeStateList`, and the `currentStatePointer` is shifted to the newly inserted Concierge state. image::UndoRedoNewCommand1StateListDiagram.png[width="800"] -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`. +Step 3. The user executes `add n/David ...` to add a new guest. The `add` command also calls `Model#commitConcierge()`, causing another modified Concierge state to be saved into the `conciergeStateList`. image::UndoRedoNewCommand2StateListDiagram.png[width="800"] [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`. +If a command fails its execution, it will not call `Model#commitConcierge()`, so Concierge state will not be saved into the `conciergeStateList`. -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. +Step 4. The user now decides that adding the guest was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoConcierge()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous Concierge state, and restores Concierge to that state. image::UndoRedoExecuteUndoStateListDiagram.png[width="800"] [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. +If the `currentStatePointer` is at index 0, pointing to the initial Concierge state, then there are no previous Concierge states to restore. The `undo` command uses `Model#canUndoConcierge()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. The following sequence diagram shows how the undo operation works: image::UndoRedoSequenceDiagram.png[width="800"] -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. +The `redo` command does the opposite -- it calls `Model#redoConcierge()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores Concierge to that state. [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. +If the `currentStatePointer` is at index `conciergeStateList.size() - 1`, pointing to the latest Concierge state, then there are no undone Concierge states to restore. The `redo` command uses `Model#canRedoConcierge()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. -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. +Step 5. The user then decides to execute the command `list`. Commands that do not modify Concierge, such as `list`, will usually not call `Model#commitConcierge()`, `Model#undoConcierge()` or `Model#redoConcierge()`. Thus, the `conciergeStateList` remains unchanged. image::UndoRedoNewCommand3StateListDiagram.png[width="800"] -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. +Step 6. The user executes `clear`, which calls `Model#commitConcierge()`. Since the `currentStatePointer` is not pointing at the end of the `conciergeStateList`, all Concierge 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::UndoRedoNewCommand4StateListDiagram.png[width="800"] @@ -302,28 +307,823 @@ image::UndoRedoActivityDiagram.png[width="650"] ===== Aspect: How undo & redo executes -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire Concierge. ** 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). +** Pros: Will use less memory (e.g. for `delete`, just save the guest being deleted). ** Cons: We must ensure that the implementation of each individual command are correct. ===== Aspect: Data structure to support the undo/redo commands -* **Alternative 1 (current choice):** Use a list to store the history of address book states. +* **Alternative 1 (current choice):** Use a list to store the history of Concierge 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`. +** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedConcierge`. * **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[] +// tag::add[] +=== Adding a Booking + +The `add` command is used by the receptionist to add the guest to the hotel, +and assign him a room. + +==== Current Implementation +We currently accept a `Guest`, `RoomNumber` and `BookingPeriod` +as parameters for the `AddCommand` constructor. An example of its usage: +`add n/Madith p/83141592 e/madith@themyth.com r/041 from/29/11/2018 to/ 03/12/2018` + +The parsing of the `AddCommand` is very similar to what was already +implemented in AddressBook4. More parameters were added, namely the +`RoomNumber` and `BookingPeriod`. These are parsed to create the respective +objects - `Guest`, `RoomNumber` and `BookingPeriod`. + +* In v2.0, users can enter a start date and duration to specify their booking +period. + +As in AddressBook4, the `Logic` component parses the `AddCommand`, and the +`Model` handles its execution. + +* In the `Model`, the `Guest` is _no longer_ added to Concierge. It was +previously the case in AddressBook4. +* A new `Booking` object is created with the `Guest` and `BookingPeriod` as +its parameters. +* This `Booking` is then added to the `Room` with the `RoomNumber` specified. Every `Room` maintains a `SortedSet` which is encapsulated in the + `Bookings` (plural) class. + +An Activity Diagram for the execution of `AddCommand#execute` is shown below. + +image::AddCommand-activity-diagram.png[width="400"] + +[NOTE] +The `AddCommandParser` already checks that `ROOM_NUMBER` is a valid +string from `001` to `100`, and the initialisation of Concierge checks that +there are 100 rooms. The `RoomNotFoundException` is not expected to occur +for any user input, but is left there as a defensive measure. + +==== Design Considerations +===== Aspect: Check for outdated bookings + +Outdated bookings are those which have a start date before today. Concierge +disallows users to `add` outdated bookings. + +* **Alternative 1 (current choice):** Do the check in `AddCommand#execute` +** Pros: Very easy to implement. A parameter check in the `execute` method will +suffice. Will only affect the `AddCommandSystemTest`. +** Cons: The actual `Model#addBooking` does not do any check on the +`BookingPeriod` being outdated, opening the possibly of outdated `Booking` s +being added from via other commands. + +* **Alternative 2:** Do the check in `Room#addBooking` +** Pros: Centralises exceptions thrown related to bookings in the +`Booking` class. Increases the cohesiveness of this class. +** Cons: All the existing tests and sample data calling the `addBooking` +method with outdated bookings have to be changed. It also becomes difficult +to do unit tests on checking in bookings which are outdated but not expired, +since these bookings can no longer be added to the model. + +===== Aspect: Reduce coupling between `Room` and `Guest` + +Semantically, we can observe a strong coupling and dependency between `Room` +and `Guest`. A `Room` contains a `Guest`, and a `Guest` also has a `Room`. +Maintaining this coupling allows for very quick lookup both ways, either +given a `Guest` (which is common at the reception desk) or given a `Room` +(which is common for housekeeping). + +* **Alternative 1 (current choice):** Add `Guest` as a field in `Room` +** Pros: An efficient way for managing bookings. Receptionist can quickly +determine if the `Room` is free to book. Lookup time for `Guest` not +expected to increase greatly, since `Room` s are not expected to have a large + number of advanced bookings made. +** Cons: Difficult to find the `Room` given the `Guest`. When a `Guest` has made +an advanced booking and wishes to cancel it, we have to search through all +the `Room` s. Nevertheless, we expect most guests to be aware of their rooms. + +* **Alternative 2:** Add `Room` as a field in `Guest` +** Pros: Very customer-centric design. Centralises all the information about +the `Guest`, including `Booking` s made and `Expense` s incurred. +** Cons: Making a new `Booking` with a `Guest` is highly inefficient. +`Booking` information is now scattered across individual `Guest` s. +*** [v1.4] On top of the list of rooms, we maintain a separate list of +checked-in guests. This list does not retain any booking information, as it + is meant to for a quick lookup of the guests' particulars. +// end::add[] + +// tag::list[] +=== Rooms List Feature +The rooms list feature builds upon, and reuses functions from the ;originally implemented ListCommand. + +The Activity UML Diagram for the current implementation of ListCommand is as follows: + +image::ListCommandUml.png[width="400"] + +==== Current Implementation +The list function is facilitated by a modified `ListCommand` class, of which the input from the CommandBox is parsed by a `ListCommandParser` class. + +The list function now requires a flag after the 'list' command. Below are the two allowed list commands: + +* `list -g` - Lists all guests. +* `list -cg` - Lists all checked-in guests. +* `list -r` - Lists all rooms. + +A `ListCommandParser` class was created to obtain and compare the flags from inputs, which required a different approach to the rest of the commands. The input string is simply split using a String function, obtaining an array of strings, of which the flags will be at index 1. + +Modification of existing FXML files, and creation of new FXML files was done to achieve separate listing of guests and rooms, and the browser panel was replaced with a panel to focus on, and display more detailed information on the selected guest/room. + +In order to stack the UI elements on top of one another to reuse and display the separate lists under the same column, modifications were made to the `MainWindow.fxml` file. +The GuestListPanel and RoomListPanel each has a "VBox" element encapsulating them, which visibility is toggled and the element itself enabled or disabled based on the flag that was obtained from the parser. This feature extends to the GuestDetailedPanel and RoomDetailedPanel and is achieved in the same way. + +==== Design Considerations +===== Aspect: How to display each list +* **Alternative 1 :** Maintain two columns on the MainWindow UI to display both rooms and guests +** Pros: Easier to modify UI by adding on instead of modifying and replacing, and modifications in the future will not be too tedious. +** Cons: UI looks cluttered with an empty column when not displaying the other, not an efficient use of screen space. + +* **Alternative 2 (current choice):** Separately display the two lists within the same MainWindow UI space/column. +** Pros: Cleaner looking, fully utilises empty spaces. Better visual feedback from commands as inputs. +** Cons: Requires heavy modification of MainWindow UI files, future features must stick with the restriction of having a list of either guests or rooms. +// end::list[] + +// tag::find[] +=== Find Feature +The Find feature expands upon the originally implemented FindCommand, allowing for the searching of both rooms and guests, with several filters. + +The Activity UML Diagram for the current implementation of FindCommand is as follows: + +image::FindCommandUml.png[width="400"] + +==== Current Implementation +The find function is facilitated by a modified `FindCommand` class, of which the input from the CommandBox is parsed by a `FindCommandParser` class. + +The find function now has the ability to find either guests or rooms. The starting commands for the find function with flags are as follows: + +* `find -g` - Find guests. +* `find -cg` - Find checked-in guests. +* `find -r` - Find rooms. + +The above command must be followed up by at least 1 filter, and they are as follows: + +Guest Filters (-g): + +* `n/ - Name` +* `p/ - Phone Number` +* `e/ - Email Address` +* `t/ - Tags` + +Room Filters (-r): + +* `r/ - Room Number` +* `c/ - Capacity` +* `t/ - Room Tags` +* `n/ - Name of guest with bookings` + +The following are filters for room bookings. The flags cannot be mixed. +The flags can be used independently, or with a from/to specified date. +Input dates must be in DD/MM/YY format. + +* `-hb - Has Bookings Flag` +* `-nb - No Bookings Flag` +* `from/ - Booking Start Date` +* `to/ - Booking End Date` + +The FindCommandParser uses a tokenizer to obtain the individual arguments/filters, whether the filter is present or not. If a filter is present, the input that precedes the filter prefix will be used to create the individual predicate class. + +These predicate classes are collected into a list of predicates before they are combined and merged in the FindCommand class. The combined final predicate is then passed to the Model Manager to filter the guest/room list, and a listingChangedEvent is called to update the UI elements. + + +==== Design Considerations + +===== Aspect: OR/AND Searching +When searching, a few things have to be considered. Does the filter specified have an OR relationship with one another, or an AND relationship. +An example is this: find -g n/Alex t/VIP, this can be interpreted in two ways. Finding guests with name as "Alex" AND with tag "VIP", or name "Alex" or tag "VIP. +// end::find[] + +// tag::checkin[] +=== Room Check-in/Checkout feature +==== Current Implementation + +The room check-in and checkout features makes use of `UniqueRoomList`. +The logic that supports the check-in and checkout operations mainly reside in the `Concierge` and `Room` classes. + +* `UniqueRoomList#checkin(RoomNumber)` -- Checks in the first booking of the room identified by the given room number +* `UniqueRoomList#checkout(RoomNumber)` -- Checks out the first booking of the room identified by the given room number +* `UniqueRoomList#checkout(RoomNumber, LocalDate)` -- Checks out the room's booking whose start date matches the given date + +[NOTE] +Active booking refers to a booking that includes today's date. + +First booking refers to the earliest (i.e. first in chronological order). +[NOTE] +A room can be checked out regardless of its checked-in status. Thus, `checkout` doubles as a command to delete bookings. + +These operations are exposed in the `Model` interface as `Model#checkInRoom` and `Model#checkoutRoom` respectively. + +Given below is an example usage scenario and the flow of the check-in feature. + +Assuming there is a booking already added to room 001, + +* The user executes `checkin r/001` when the guest arrives. +. The `checkin` command takes in a `RoomNumber` argument and calls `Model#checkInRoom` as such: `Model.checkInRoom(roomNumber)` +. `ModelManager#checkInRoom` (which implements Model) will call `VersionedConcierge#checkInRoom` +. `VersionedConcierge#checkInRoom` will call `UniqueRoomList#getRoom` to get the room using its RoomNumber +. `VersionedConcierge#checkInRoom` will call `Room#checkIn` +. `Room#checkIn` will +.. throw `NoBookingException` if the room has no bookings +.. throw `ExpiredBookingException` if the room's first booking is expired +.. throw `InactiveBookingCheckInException` if the room's first booking is not active +.. throw `BookingAlreadyCheckedInException` if the room's first booking is already checked in +.. update the first booking as checked-in if no exceptions were thrown, and replace the room with its updated version that has the first booking checked-in +. `VersionedConcierge#checkinRoom` will call `VersionedConcierge#addCheckedInGuestIfNotPresent` +. `VersionedConcierge#addCheckedInGuestIfNotPresent` will add the guest of the checked-in booking to the checked-in guest list + if he/she is not already in it + +The following sequence diagram shows how `CheckInCommand#execute` works, from the Logic up to the Model: + +._The sequence diagram of `CheckInCommand#execute`._ +image::CheckinCommandSequenceDiagram.png[width="1280"] + +Since the Model simply calls `VersionedConcierge#checkInRoom`, the following activity diagram will illustrate how +`Concierge#checkInRoom` works: + +._The activity diagram of checking-in a room when `Concierge#checkInRoom` is executed._ +image::check-in-activity-diagram.png[width="800"] + +==== Design Considerations + +===== Aspect: Immutability of check-in command + +* **Alternative 1 (current choice):** `checkIn` a room by creating a new copy of the room + with the `isCheckedIn` flag of the first booking set to true. +** Pros: Debugging is easy. Consistent with the rest of the application. +** Cons: `checkIn` method becomes unintuitive, since a new room is returned from the operation, + instead of a void method simply setting the instance property. +* **Alternative 2:** `checkIn` a room by setting the `isCheckedIn` flag of the first booking to true. +** Pros: Check-in method is intuitive, and does not return a new room. +** Cons: Harder to debug. Tests also become troublesome since changes are made to the same referenced room. + +===== Aspect: Deletion of bookings + +* **Alternative 1 (current choice):** Use `checkout` to delete any booking. +** Pros: `checkout` doubles as a delete booking feature, so no need for a `deletebooking` command. +** Cons: Not very natural, as `checkout` implies checking out a checked-in booking. +* **Alternative 2:** Use `checkout` to delete only active booking, and create new command `deletebooking` to delete expired and upcoming bookings. +** Pros: More natural, `checkout` can only do what its name implies. +** Cons: Need to implement new command and more methods, to support the same deletion operation but with a different name. + +// end::checkin[] + +// tag::expenses[] +=== Expense, Expenses and ExpenseType +In Concierge, users will be given the feature of tracking the expenditure of each individual +guest, in order to facilitate checkout charges. Hence, the three classes, `Expenses`, `Expense` +and `ExpenseType` have been created for this purpose. In addition, the hotel also has a +`Menu` of goods and services available. + +==== Current Implementation +`ExpenseType` objects are essentially immutable objects that represent a single item or service +being sold at the hotel. An `ExpenseType` object contains information about its menu number, +usual price, and description. The main purpose of this class is for convenience; users may +charge a customer by simply providing the menu number of the item and the cost and description +of the item will be able to be referenced. `ExpenseType` information is stored in a `Menu` +object, which is then stored on the hard disk, since users should have the ability to modify +the menu manually. The `Menu` object is internally represented with a `HashMap`, +with the menu number as keys and the `ExpenseType` objects as values. + +* **Alternative 1: Use a List to store the menu.** While there may be negligible +differences for a small menu, searching for an `ExpenseType` object still takes linear time +and there may be significant performance drops for a large menu. + +An `Expense` object contains information about one individual expenditure by a guest. An +`Expense` object encapsulates the cost, `ExpenseType` of the item bought, and the date and time +of expenditure. + +The `Expenses` object is essentially a `List`. Every room contains an `Expenses` +object, to represent the collection of all the expenses of the guests in the room. + +* **Alternative 1: Use a `List` object**: Defining the `Expenses` class allows us +to restrict access to the collection, and only allow certain methods such as adding an +`Expense` or displaying on screen. +* **Alternative 2: Use a `Set` object**: Having the expenses ordered (e.g. +chronologically) will be useful for generating a nice view of all the expenses incurred. + +Here is a simple UML describing the roles of these classes. + +image::expense_uml.png[width="600"] + +==== Design Considerations + +===== Aspect: Immutability of Menu +While it is conceivable that the items sold may change from time to time, +for various reasons such as unpopularity or seasonal products, giving users +the ability to add and remove items from the menu may result in more +problems than benefits. We expect that alterations to the menu will not be +performed frequently, and that the majority of our users, receptionists, +will not be required to add and remove items to the menu. The menu also does +not have to be altered during operational hours. Hence, by making +`Menu` immutable, we eliminate the possibility of making accidental or +unwarranted changes to the menu. The only method to modify `Menu` would +thus be through the XML file, which we believe is suitable for these +purposes. + +===== Aspect: Immutability of ExpenseType +The `ExpenseType` object is meant to hold the default values of the name and +price of each item. In other words, since an `Expense` object references an +`ExpenseType` object, the `Expense` object is allowed to have a cost that +is different from the cost in the corresponding `ExpenseType` object, to +account for cases such as the guest having a personalised discount due to +the usage of vouchers or certain credit cards. Thus, ExpenseType does not +need to be modified by users in the application. Nonetheless, it is still +possible to modify the default information through editing the XML file. + +===== Aspect: Assignment of the Expenses object +* **Alternative 1 (current choice):** Assign `expenses` to each `Room`. +** Pros: Suitable for current architecture. Each `Booking` only has one `Guest`, + and each `Guest` will only stay in one `Room`. Makes more sense to assign to + `Room` such that it represents the expenditure of the entire `Room` and not + one `Guest`, since the occupants of the `Room` can only contribute to one single + `Expenses` object. `Room` is a more natural choice over `Booking` as `Booking` + is meant to encapsulate booking information such as timing and `Room`. + Not much difference in implementation no matter which one of the three classes + it is assigned to. +** Cons: May be confusing to implement. Need to ensure that there are no expenses + for rooms that have no guests. +* **Alternative 2:** Assign `expenses` to each `Guest`. +** Pros: Can track `expenses` of each `Guest`, can find out who are the heavy + spenders. Can use this information for promotional activities such as vouchers + or membership. +** Cons: Not all guests that will stay in the hotel are registered in the guest + list, since each `Booking` only requires the name of one `Guest`, regardless of + the `Room`. Will require a major refactoring of the `add` command. Complications + may also arise if a `Guest` has multiple bookings simultaneously and there is a + need to track the `Expenses` over different rooms. +* **Alternative 3:** Assign `expenses` to each `Booking`. +** Pros: Can allow tracking of the booker's expenditure, less confusing to implement + than `Room`, can allow for expenses to be recorded before the guest checks in. +** Cons: May violate SRP, since `Booking` should ideally only deal with booking + information. +// end::expenses[] + +// tag::money[] +=== Money + +==== Current Implementation +`Money` is a class used to store monetary values. This class was created to +enforce the restriction that monetary values should always have at most two +decimal places, which could be inconvenient if using Java data types such +as `double` or `BigDecimal`. `Money` objects can be created by the user +through the `service` command (details in the next section). + +The `Money` class contains two main attributes, `dollars` and `cents`, both +of which are `int`s, since it is unlikely that the cost of any one item will +exceed `Integer.MAX_VALUE` dollars. + +The main method of creating `Money` objects is through the `service` command, +with the `Money` class parsing a string to convert into a `Money` object. +The method `isValidMoneyFormat()` handles the checking of the string format, +and the list of requirements are as follows: + +* Can be negative. +* Format of the string should be {1 to 10 digits}.{2 digits}, e.g. `12.34`. + `123`, `.50`, `12.9`, `12345612345.00` are not allowed. +* The `dollars` section can be 0 but cannot have leading 0, i.e. 0.12 is allowed + but 01.23 is not allowed. +* The `dollars` section should not exceed `Integer.MAX_VALUE`. +* Cannot have characters that are not digits or `.` or `-`. + +==== Design Considerations + +===== Aspect: Immutability of Money +`Money` does not have to be mutable. Adjustment of `Expenses` are to be done +through the `service` command. `Money` is simply a data type, much like +`Double` and `Integer`. +// end::money[] + +// tag::service[] +=== ServiceCommand + +==== Current Implementation +The `service` command is used for charging expenses to rooms. This +functionality is the main reason that the classes Expenses, Expense, +ExpenseType, Menu and Money were implemented. The format for the `service` +command is as such: + +`service r/ROOM_NUMBER no/ITEM_NUMBER [c/COST]` + +The cost is made optional for the convenience of the user. We expect that +most of the time, the cost of items are more or less fixed. Instead of +having the user type in the same cost all of the time, the field is made +optional. This functionality is enabled by the use of `ExpenseType` and +`Menu`, which stores the default cost of items. If the cost is not specified, +the default cost of the item will be used. + +As in AddressBook4, the ConciergeParser (aka AddressBookParser) will parse +the user input and create a `ServiceCommandParser` object to parse a `service` +command. The `ServiceCommandParser` is responsible for checking that the +`RoomNumber` and cost (a `Money` object) are in the correct format. Note that +the item number is not checked here, since the `Menu` object of `Concierge` +has to be available in order to check that the given item number is a valid +item. Hence, any string will be accepted by the parser. Since `ServiceCommand` +has access to the `Model` and thus the `Menu`, the checking is given to +`ServiceCommand` instead. A successful parse will then return a +`ServiceCommand(RoomNumber roomNumber, String itemNumber, Optional itemCost)` +object. + +The following flowchart describes what happens when the `execute` method +of a `ServiceCommand` is called. + +image::ServiceCommand-flowchart.png[width="600"] + +The `model.addExpense()` method call was not illustrated in detail in the flowchart, +thus it is illustrated in this sequence diagram. + +image::AddExpenseSequenceDiagram.png[width="900"] + +==== Design Considerations + +===== Aspect: Deleting and editing Expenses +* **Alternative 1 (current choice):** Use `service` to edit `Expense`s. +** Pros: Simply keying in `Expense`s with negative values is easy to implement, + and does not stray far from real-life implementations, e.g. receipts often + contain cost subtractions for discounts and promotions. +** Cons: May not be elegant, `Expenses` may become cluttered if there's too many + corrections. +* **Alternative 2:** Create new commands to edit and delete `Expense`s. +** Pros: The `Expenses` will contain all the `Expense`s at their correct prices. +** Cons: More effort to implement, difficult to implement, e.g. may need to implement + listing out all `Expense`s of a `Room` with the `list` command in order to select + the `Expense` to delete or edit. Information on discounts and corrections will + also be lost. + +// end::service[] + +// tag::loginlogout[] +=== Login and Logout + +The `login` feature allows hotel managers to control which receptionists +have full access to Concierge. When paired with the CommandArchive feature, +they can also create a blame history to trace rogue commands. + +==== Current Implementation + +Currently, `login` is implemented as a +<>, +so users are not prompted to sign in upon starting Concierge. Instead, they +only have to sign in when executing commands which would mutate the data, +such as `add`, `checkin`, `checkout`, `reassign`, `service` and `clear`. + +===== Logic + +Given the nature of the `login` command being dynamic (can be entered at any +point in time, between any commands), it is then natural to implement it like +a normal command, extending the abstract `Command` class. The `logout` command + is also implemented in this way. + +===== Model + +The model handles the signing-in, using its attribute `LogInManager`. + The Class Diagram of the login module is shown below. + +image::LogInCommand-LogInManager-classdiagram.png[width=700] + +`LogInManager` uses an optional `username` to keep track of whether +the user is currently signed in. The `passwordReferenceList` provides an +immutable key-value lookup for usernames and passwords. + + +`LogInManager` implements the following operations. + +* `LogInManager#isSignedIn()` - checks if the user is currently signed in. +* `LogInManager#signIn(String, String)` - attempts to sign in with the given +username and hashed password. This is handled by the `PasswordHashList`. A +case-insensitive comparison is used on the hash. +* `LogInManager#signOut()` - signs out of Concierge. + +A new method `resetUndoRedoHistory` was added to the +`VersionedConcierge` (used for the Undo/ Redo feature). This is used to clear +the command history upon a `logout` command, so users cannot undo important +commands or redo accidental bad commands after signing out. + +====== Login + +Shown below is the Sequence Diagram for executing a valid `login` command. +The diagram also illustrates how the `LogicManager` checks for the sign-in +requirement of commands. + +image::LogInCommand-sequencediagram.png[width=1000] + +===== Storage + +The `passwords.json` file is read when Concierge is first opened (i.e. in +`MainApp#init`), and is never written to again. The storage function is +managed by the `JsonPasswordsStorage` class. Intuitively, passwords are stored as +<> for quick look-up. + +A SHA-256 hash was used in building this feature. In future, this hashing +algorithm can be changed to a HMAC hash, which adds a username salt. Then, +different users will not know if they have chosen the same passwords. + +===== Check for sign-in requirement of commands + +`LogicManager` does the +<>, + and whether the model is signed in. + +The `Command` class exposes a new `requiresSignIn()` method that returns +false by default. To make new command require signing-in, one only has to +overwrite this method in that command. + +==== Design Considerations + +===== Aspect: Accessing features of Concierge with/ without login + +* **Alternative 1 (current choice):** Login is needed only for some features +** Pros: Manager can implement some level of access control within Concierge, + especially since some of the more commonly used Concierge features (`list`, + `find`) are read-only features. This is quicker than mandating a sign-in at + the start and creating different user views based on the account privilege (admin vs normal). +** Cons: Not very intuitive to users. They have to enter commands before being +told they need to sign in. The `requiresSignIn()` check takes place after the +parsing of the command, so a user can is told they cannot execute the command +without a sign-in after their command is parsed correctly. + +* **Alternative 2:** Login is needed for all features +** Pros: Very easy to check login validity. This only occurs when Concierge is +first loaded. Subsequent commands can be executed without additional checks +on the sign-in requirement. + +===== Aspect: Check for sign-in requirement of commands + +Given that sign-in is only required for some commands, the priority in +designing this aspect is the ability to easily mandate/ disable compulsory +the login requirement for current and future commands. + +* **Alternative 1 (current choice):** Do the check in `LogInManager#execute` +** Pros: Ensures that commands are checked before any execution. Users will not +inadvertently change the model before doing the sign-in checks. +** Cons: Unable to implement commands that can do some actions without +sign-in. For example, a future developer may want to make the `add` command +such that when the user is not signed-in, the booking is still added but a tag + is added to the `Guest`, reminding the manager to verify the booking. +*** Violates the Single Responsibility Principle. The job of `LogicManager` +is to parse and execute commands. + +* **Alternative 2:** Do the check in `Command#execute` +** Pros: Increases cohesiveness of `Command` class. The compulsory sign-in is + an attribute of a `Command`, so these checks can be done internally. + `Command` can implement a method `checkSignIn(Model)`, and commands which + require sign-ins can call this method in their respective `execute` methods. +** Cons: While increasing cohesion, this implementation makes less semantic sense. + The logical misstep comes because one is executing the method, then checking + if the method can be executed, then "reversing" the execution. + +===== Aspect: Storage of Passwords + +The password file is currently read at `MainApp#init`, and saved once. +Unlike the Concierge data, this file is no longer referred to when Concierge +is in use. + +* **Alternative 1 (current choice):** Store passwords in JSON file +** Pros: JSON is very easy to work with. +*** Able to utilise existing `JsonUtil` methods used by the `UserPrefs` and +`Config` classes. +*** Easily parse data into key-value pairs, which semantically matches our needs. +** Cons: `JsonUtil` file is not completely suitable for a data type that has +potentially an unlimited number of entries, since this utility serialises the + data to match the class attributes. +* **Alternative 2:** Store passwords in same XML file as all other +Concierge data +** Pros: Centralises data storage in Concierge. There is only one single +source of truth for all data. +** Cons: The XML file is too complicated for the needs of password storage. +*** Concierge does not need to write the the passwords file when in use. +`concierge.xml` is constantly being written to, which is an unnecessary and +possibly unsafe feature for the passwords component. +*** Creating a new password entry is difficult since once has to add all +the layers of XML tags involved. Nevertheless, users are not expected to be +adding new accounts on a regular basis. +// end::loginlogout[] + +// tag::autocomplete[] +=== Autocomplete: kbd:[Ctrl], kbd:[Alt] + +==== Overview + +The Autocomplete feature allows the user to seamlessly type in the +full command and prefixes without having to worry if he/she missed +out on any prefix. This feature helps the user by prompting the correct +format. This is useful as some of the commands require several inputs +from the user and hence this will save time and commands can be +executed faster. + +A quick-clear has also been added as part of this feature, so that +the user can again, save time.Press kbd:[Alt] to quick-clear the +`CommandBox` (saves time for user when he wants to clear the box). + +The command box before kbd:[Alt] is pressed: + +image::servicepreclear.png[width="419"] + +The command box aft kbd:[Alt] is pressed: + +image::servicepostclear.png[width="371"] + +==== Example of how feature works + +**Step 1**: Launch application + +**Step 2**: User enters `a` in `CommandBox` then presses kbd:[Ctrl]. +`AutoCompleteManager()` compares `input` through the list of +`initCommandKeyWords`and proceeds to display the command in the +`CommandBox` because `a` is an applicable `COMMAND_WORD`. + +image::add.png[width="579"] + +After kbd:[Ctrl] has been pressed, it automatically inserts the +first prefix `PREFIX_NAME` in the command line. + +image::addPREFIX_NAME.png[width="581"] + +**Step 3**: After the user fills up the `PREFIX_NAME` field, he can press +kbd:[Space] to move on to the next prefix. After pressing kbd:[Space], then +he can press kbd:[Ctrl]. At this point, `AutoCompleteManager()` is +called again but this time instead of calling the `getAutoCompleteCommands()` +it calls `getAutoCompleteNextMissingParameter` since it will detect +the presence of the `PREFIX_NAME` parameter. + +This is the expected outcome before pressing kbd:[Ctrl] + +image::anthonyspace.png[width="581"] + +This is the expected outcome after pressing kbd:[Ctrl] + +image::afteranthonyspace.png[width="581"] + +**Step 4**: The user repeats Step 3 until all parameters are input by the user +and then presses kbd:[Enter] to execute the command. +Note: For `AddCommand`, the final parameter `PREFIX_TAG` is optional, so +the user can just delete it if he chooses not to add a tag. + +This is the expected outcome after all the parameters are filled. + +image::fulladdautocomplete.png[width="953"] + +Press kbd:[Enter] to execute the command. + +Given below is the activity diagram for the Autocomplete feature. + +**Activity Diagram :** + +image::AutocompleteActivityDiagram.png[width="331"] + +Activity Diagram demonstrates what happens when user presses kbd:[Ctrl] + +==== Current Implementation + +The Autocomplete mechanism is facilitated by `AutoCompleteManager`, which +can be found in `LogicManager`. It supports the auto completion of incomplete +commands by providing a list of auto completed commands from a given incomplete +command. + +An underlying `Trie` data structure is used to facilitate the `AutocompleteManager`. +`Trie` only supports autocompletion of commands that are provided by the +`AutocompleteManager`. The `CommandParameterSyntaxHandler` that is found in +`AutocompleteManager` supports the autocompletion of parameters for commands. + +Given below is the class diagram for the Autocomplete feature. + +**Autocomplete Class Diagram :** + +image::AutocompleteClassDiagram.png[width="719"] + +The `CommandBox` interacts with the `AutocompleteManager` using `LogicManager`. +When the user presses kbd:[Ctrl] in the command box, the `CommandBox` will +handle the Ctrl key press event and will execute the `AutoCompleteUserInput()` +method. + +Given below is the sequence diagram for the Autocomplete feature. + +**Autocomplete Sequence Diagram :** + +image::AutocompleteSequenceDiagram.png[width="926"] + +==== Design Considerations + +===== Aspect: Implementation of Autocomplete + +* **Alternative 1 (current choice): ** Use a manager (`AutoCompleteManager`) +to handle the autocomplete helper methods. + +** Pros: +Allows for better usability and more code abstraction. +** Cons: +The amount of time taken for a new developer to to understand all the +interaction between methods will be longer. + +* **Alternative 2: ** Iterate through all possible commands to find match prefix. + +** Pros: +Implementation of this alternative would be easier. +** Cons: +If there are too many commands being input consecutively, the application +might start to lag due to possible loss of performance. + +===== Aspect: Implementation of Algorithm + +* **Alternative 1 (current choice): ** Trie Data Structure + +** Pros: +Performance of application will be better. +** Cons: +The complexity of implementation is higher. + +* **Alternative 2: ** Iterate through all possible commands to find +match prefix. + +** Pros: +Implementation of this alternative would be easier. +** Cons: +If there are too many commands being input consecutively, the application +might start to lag due to possible loss of performance. + +// end::autocomplete[] + +// tag::archive[] +=== [Proposed] Command Archive feature +Given below is the UML diagram for the `CommandArchive` Class: + +image::CommandArchive_class.png[width="280"] + +Given below is the UML diagram for the `CommandHistory` Class: + +image::CommandHistory_class.png[width="345"] + +==== Current Implementation + +The Command Archive mechanism is facilitated by `CommandArchive`. +It utilises the `userInputHistory` to extract the latest command that the user +has input and passes the `inputString`to `stringToFile` method in `CommandArchive` +class. The `inputString` is then appended to the `CommandFile.txt` file. +Additionally, it implements the following operations: + +* `StringBuilder()` -- The main operations of the `StringBuilder` are the `append` +and `insert` methods which can be overloaded to accept data of any type. The `append` +method always adds these characters at the end of the builder.This operation +can be found in `CommandHistory`. + +* `toString()` -- Converts the StringBuilder object into a string named +`inputString` so it can be passed to the `CommandArchive` class. This operation +can be found in `CommandHistory`.* `getLogger()` -- Creates `LOGGER` so that it +can log any `IOExceptions` that are caught in the catch blocks of the methods +found in `stringToFile` method of `CommandArchive`. + +* `substring()` -- Extracts the latest command from the `userInputHistory`. +This is required because the `userInputHistory` appends all the older commands +into the LinkedList as well. This is done by looking for the first newLine character +occurrence of the `inputString`. The substring is then extracted as +`latestUserCommand`. This operation can be found in `CommandArchive`. + + * `simpleDateFormat()` -- Creates a `timeStamp` in DD/MM/YYY format that can later + be appended to `latestUserCommand`. This operation can be found in `CommandArchive`. + + * `fileWriter` -- Writes the stream of characters (which is `latestUserCommand`) +to `commandHistory` file. This will eventually be the output that is written into +`commandFile.txt` via `PrintWriter`. The `PrintWriter` also appends `timeStamp` +to the latest entry (which is eventually `timeStamp` + `latestUserCommand`). This +operation can be found in CommandArchive`. + + This command requires a login. + +==== Design Considerations + +===== Aspect: How to extract userInputHistory + +* **Alternative 1 (current choice):** `userInputHistory` is first put into a +`stringBuilder` and then converted to string to then pass to `CommandArchive`. + +** Pros: +1. Easy to implement because `StringBuilder` can utilise `append` and `insert` +methods, which can be overloaded to accept any data. +2. Faster than `StringBuffer` under most implementations. +3. StringBuilder is mutable while String is immutable. +** Cons: String is more optimised especially if you don't need the extra features +of `StringBuilder` +* **Alternative 2:** Create a KeyLogger class that implements KeyListener +to capture userInput. +** Pros: It is more secure and can only be accessed for audits and other +administrative access purposes and is hidden from the user. +** Cons: +1. If implemented wrongly, it will become a global KeyLogger that captures +userInput outside of application. + +2. Does not utilise the existing infrastructure +and data found in the base level program class `CommandHistory` and hence +would require more effort to implement. +// end::archive[] + // tag::dataencryption[] === [Proposed] Data Encryption _{Explain here how the data encryption feature will be implemented}_ - // end::dataencryption[] === Logging @@ -528,400 +1328,568 @@ Here are the steps to create a new release. === Managing Dependencies -A project often depends on third-party libraries. For example, Address Book depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + +A project often depends on third-party libraries. For example, Concierge depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + a. Include those libraries in the repo (this bloats the repo size) + b. Require developers to download those libraries manually (this creates extra work for developers) -[[GetStartedProgramming]] [appendix] -== Suggested Programming Tasks to Get Started +== Product Scope + +*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 +* is reasonably comfortable using CLI apps + +*Value proposition*: manage contacts faster than a typical mouse/GUI driven app + +[appendix] +== User Stories -Suggested path for new programmers: +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <>. +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |receptionist |retrieve the room number of a guest |provide any kind of services to the guest in his/her room -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <> explains how to go about adding such a feature. +|`* * *` |receptionist |view the prices of different room types |inform the guests of the prices of different rooms -[[GetStartedProgramming-EachComponent]] -=== Improving each component +|`* * *` |receptionist |mark rooms in need of maintenance or cleaning |prevent guests from staying in those rooms -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). +|`* * *` |hotel manager |know which rooms' guests are checking out on a certain day |assign cleaning staff to those rooms -[discrete] -==== `Logic` component +|`* * *` |receptionist |look at the available rooms of a certain type in a certain time |designate rooms for guests -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. +|`* * *` |receptionist / hotel manager |know the room services called by a guest |charge the guest accordingly -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. +|`* * *` |receptionist |keep track of the particulars of guests such as home address, phone number, room number |contact them in the event of emergencies before, during or after their stay -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* 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. -* 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. -** 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. -**** +|`* * *` |receptionist |be informed of the guests that checked out late |charge them with a late check-out fee -[discrete] -==== `Model` component +|`* * *` |receptionist |swap rooms for guests |allow guests to change rooms if they report any damages -*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. +|`* * *` |receptionist |manually reduce / extend the stay of a guest (including late check-out requests) |allow guests to change their check-out timing -[TIP] -Do take a look at <> before attempting to modify the `Model` component. +|`* * *` |receptionist |modify a guest's particulars |correct errors without rewriting the entry -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* 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. -** 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. -* 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)`. -** 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. -**** +|`* * *` |receptionist / hotel manager |remove a guest's entry |facilitate the check-out procedure + +|`* * *` |receptionist |reserve rooms for guests |allow guests to place bookings + +|`* * *` |receptionist |cancel bookings on request |let other guests occupy the room + +|`* * *` |hotel manager |export the guests' profiles |keep an archive + +|`* * *` |hotel manager |look at all financial transactions made between guests and the hotel |facilitate the monthly audit + +|`* * *` |receptionist / hotel manager |red flag problematic guests and write descriptions on them |warn the staff of problematic guests + +|`* * *` |receptionist |filter rooms by type, occupancy status, number of guests, etc. |understand the current state of occupancy + +|`* *` |hotel manager |know the usage statistics of facilities |plan for budget and staff allocation + +|`* *` |hotel manager |adjust the room rates |take advantage of seasonal pricing + +|`* *` |hotel manager |send my guests a "Thank You" note upon check-out |maintain good relations with them + +|`* *` |hotel manager |backup my data |prepare for data corruption accidents + +|`* *` |receptionists |convert room rates to common global currencies |help guests better understand the pricing + +|`*` |hotel manager |know the average amount spent by guests who checked out in the current month |evaluate the effectiveness of short-term events + +|`*` |hotel manager |look at which receptionist last edited a reservation or stay |hold the receptionists accountable if mistakes were made +|======================================================================= + +[appendix] +== Use Cases [discrete] -==== `Ui` component +=== UC1.1: Check-in a Guest -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. +*System*: `Concierge`, *Actor*: `Receptionist` -[TIP] -Do take a look at <> before attempting to modify the `UI` component. +*MSS* -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. +1. Receptionist checks the room rates for all room types +2. Receptionist checks available rooms (not occupied or reserved) of the type guest wants +3. Receptionist ensures that room has all necessary maintenance completed +4. Receptionist assigns room to Guest + -**Before** +Use case ends. + +*Extensions* + +[none] +* 2a. System indicates that there are no rooms available + -image::getting-started-ui-tag-before.png[width="300"] +Use case ends. + +* 4a. There are multiple Guests to be checked-in + -**After** +[none] +** 4a1. Receptionist adds all Guests to System + -image::getting-started-ui-tag-after.png[width="300"] +Use case resumes at step 4. + +[discrete] +=== UC1.2: Retrieve room number of a Guest + +*System*: `Concierge`, *Actor*: `Receptionist` + +*MSS* + +1. Receptionist searches room number using Guest’s particulars (e.g. Name, ID, Phone Number, etc.) +2. System returns room number + -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** - -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). +Use case ends. + +*Extensions* + +[none] +* 2a. System indicates that the Guest is not staying in the hotel. + -**Before** +Use case ends. + +[discrete] +=== UC1.3: Send room service to a Guest + +*System*: `Concierge`, *Actor*: `Receptionist` + +*MSS* + +1. Receptionist retrieves room number of Guest (UC1.2) +2. Receptionist specifies what type of room service to send to Guest +3. System confirms room service sent to guest, with an ETA + -image::getting-started-ui-result-before.png[width="200"] +Use case ends. + +*Extensions* + +[none] +* 3a. System indicates that there are no available hotel attendants at the moment + -**After** +[none] +** 3a1. Receptionist puts Guest on a waiting queue + -image::getting-started-ui-result-after.png[width="200"] +Use case ends. + +[discrete] +=== UC1.4: Swap Guest's room + +*System*: `Concierge`, *Actor*: `Receptionist` + +*MSS* + +1. Receptionist views listing of available rooms (UC1.2) +2. Receptionist swaps guest’s room +3. System prompts to mark the vacated room for housekeeping +4. Receptionist sends for housekeeping service in vacated room + -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** 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. -** 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. -**** - -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. +Use case ends. + +[discrete] +=== UC1.5: Edit Guest's personal particulars + +*System*: `Concierge`, *Actor*: `Receptionist` + +*MSS* + +1. Receptionist identifies Guest using personal particulars (e.g. name, ID, phone number) +2. Receptionist updates Guest details + -**Before** +Use case ends. + +[discrete] +=== UC1.6: Reserve a room for Guest + +*System*: `Concierge`, *Actor*: `Receptionist` + +*MSS* + +1. Receptionist views listing of available rooms (UC1.2) +2. Receptionist specifies reservation dates + -image::getting-started-ui-status-before.png[width="500"] +Use case ends. + +[discrete] +=== UC2.1: Edit room rates + +*System*: `Concierge`, *Actor*: `Hotel Manager` + +*MSS* + +1. Hotel Manager checks the room rates for all room types +2. Hotel Manager specifies new room rate for a particular room type + -**After** +Use case ends. + +*Extensions* + +[none] +* 2a. Hotel Manager specifies an invalid room rate (has to be non-negative integer) + -image::getting-started-ui-status-after.png[width="500"] +[none] +** 2a1. System displays an error message that no changes have been made + -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** 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. -** 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. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** +Use case ends. [discrete] -==== `Storage` component +=== UC2.2: Check statistics -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. +*System*: `Concierge`, *Actor*: `Hotel Manager` -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. +*MSS* -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. +1. Hotel Manager specifies periodicity of earnings to checked-in + -**** -* 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. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** +Use case ends. -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` +_{More to be added}_ -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. +[appendix] +== Non Functional Requirements -*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. +. Should work on any <> as long as it has Java `9` or higher installed. +. Should be able to hold up to 1000 guests without a noticeable sluggishness in performance for typical usage. +. Command Line Interface is the primary mode of input. There is a preference for typing over mouse actions or key combinations. One-shot commands are preferred over multi-step commands. +. 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. +. Incremental development: a reasonably consistent delivery rate is expected. +. The data should be stored locally and should be in a human editable text file, so that advanced users can manipulate the data by editing the file. +. The software should follow the Object-oriented paradigm. +. The project will not use a DBMS. +. The software should be platform-independent. +. The software should work without requiring an installer. -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` +_{More to be added}_ -Examples: +[appendix] +== Glossary -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. +[[mainstream-os]] Mainstream OS:: +Windows, Linux, Unix, OS-X -==== Step-by-step Instructions +[[private-contact-detail]] Private contact detail:: +A contact detail that is not meant to be shared with others -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. +[[guest]] Guest:; +A guest staying in the hotel -**Main:** +[[receptionist]] Receptionist:: +Staff at the counter, in-charge of check-in and check-out procedures. They occasionally receive calls from potential guests -. 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`. +[[hotel-manager]] Hotel Manager:: +The one guest in-charge of the entire hotel. Manages staff, guests and facilities -**Tests:** +[[housekeeping]] Housekeeping:: +Staff in-charge of cleaning rooms and restoring them to the default configuration for a new Guest to stay -. 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`. +[[facilities]] Facilities:: +Facilities: Any form of services provided within the hotel. These include the spa, gym, game room, casino, laundry, bar, restaurants, etc -===== [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.` +[appendix] +== Product Survey -**Main:** +*Cloudbeds* -. 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`. +* Easy to use: staff are able to learn how to operate the system with minimal training +* Access to leading travel channels (e.g. booking.com) +* Custom payment options +* Group analytics +* Global currency support -**Tests:** +*eZee Frontdesk* -. 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. +* Booking Engine, Channel Manager and Restaurant POS in one system +* Rate management to maximise revenue through seasonal stay rates -===== [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. +*Frontdesk Anywhere* -**Main:** +* Export guest profiles +* Data encryption and privilege control for users +* Sends "Thank You" letters to guests after their stay -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. +*Hotelogix* -**Tests:** +* Multi-device booking engine -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +*MSI CloudPM* -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +* Automatic back-up on the cloud +* Access to archived night audit reports -**Main:** +*roomMaster* -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +* Complete audit trail for all financial transactions +* Guest history available -**Tests:** +[appendix] +== Market Survey -. Add test for `Remark`, to test the `Remark#equals()` method. +*Hotel Rendezvous* -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +* Interviewed via phone about hotel operations +* For instance, using Hotel Rendezvous as reference, we discovered that hotels only require 1 guest of a booking to provide +identifications details, thus the responsibility of the guest(s) under that booking falls to him/her. -**Main:** +[appendix] +== Instructions for Manual Testing -. 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.) +Given below are instructions to test the app manually. -===== [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. +=== Autocomplete -**Main:** +Enter `a` in the command box and press kbd:[Ctrl] + +Add command word is autocompleted with n/ prefix. -. Add a new Xml field for `Remark`. +Fill in a name, press kbd:[Space] and kbd:[Ctrl] + +You will be prompted with the next parameter. -**Tests:** +Optional parameters (e.g. [t/TAG] in `add`) are at the back, and are up to you + to include. -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +Press kbd:[Alt] to clear your command box. -===== [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`]. +Enter `c` in the command box and press kbd:[Ctrl] + +No autocompletion is in place because there is no unique command that starts +with `c`. `checkin` and `checkout` are both possible options. -**Tests:** +=== Adding a Booking -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +`add n/Pikachu p/81726354 e/pi@ka.chu t/wheelchair r/047 from/20/11/2018 to/23/11/2018` + +Unable to execute command because you have not signed-in to Concierge. -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +`login user/admin pw/passw0rd` + +Successfully signed-in to Concierge. The previous booking can be successfully + made. -**Main:** +`add n/Pikachu p/81726354 e/pi@ka.chu r/049 from/20/11/2018 to/23/11/2018` + +Pikachu can make a new (inactive)** booking in the same time period but a +different room. -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +`add n/Pikachu p/81726354 e/pi@ka.chu r/049 from/16/11/2018 to/20/11/2018` + +Pikachu can make a new (active)** booking. -**Tests:** +`add n/Pikachu p/81726354 e/pi@ka.chu r/049 from/19/11/2018 to/21/11/2018` + +Unable to make booking because it overlaps with the previous booking. -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +`add n/Pikachu p/81726354 e/pi@ka.chu r/047 from/14/11/2018 to/17/11/2018` + +Unable to make booking because it is outdated. -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +** As of CS2103 Practical Exam which is expected to be on 16 November 2018. -**Main:** +=== Check-in -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +First, ensure that you have signed in by doing `login user/admin pw/passw0rd`. -**Tests:** +* Checking in a room that has an active first booking (i.e. booking period includes today's date) +** Prerequisites: There is a booking in the room you want to check in. Add an active booking to an empty room by doing: + +`add n/Pikachu p/81726354 e/pika@chu r/010 from/16/11/2018 to/20/11/2018` +** Test case: `checkin r/010` + +Expected: Checks-in the guest Pikachu's booking and adds him to the checked-in guest list. -. Update `RemarkCommandTest` to test that the `execute()` logic works. +* Checking in a room that has an expired first booking (i.e. ends before today's date) +** Prerequisites: There is a booking in the room you want to check in. Room 006 has a preloaded expired booking for you to test. +** Test case: `checkin r/006` + +Expected: Cannot check-in room 006 as first booking is already expired. -==== Full Solution +* Checking in a room that has a upcoming first booking (i.e. starts after today's date) +** Prerequisites: There is a booking in the room you want to check in. Add an upcoming booking to an empty room by doing: + +`add n/Pikachu p/81726354 e/pika@chu r/011 from/01/12/2018 to/02/12/2018` +** Test case: `checkin r/011` + +Expected: Cannot check-in room 011 as first booking is not active. -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +* Refer to the user guide to test more edge cases -[appendix] -== Product Scope +[NOTE] +These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. -*Target user profile*: +=== Checkout -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +First, ensure that you have signed in by doing `login user/admin pw/passw0rd`. -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +* Checking out first booking of a room that is not checked-in +** Prerequisites: There is a booking in the room you want to check out. Add a booking to an empty room by doing: + +`add n/Pikachu p/81726354 e/pika@chu r/020 from/16/11/2018 to/20/11/2018` +** Test case: `checkout r/020` + +Expected: First booking is deleted. Guest of booking is not added into checked-in guest list nor archived guest list. -[appendix] -== User Stories +* Checking out first booking of a room that is checked-in +** Prerequisites: There is a booking in the room you want to check out. Add an active booking to an empty room by doing: + +`add n/Raichu p/81726354 e/pika@chu r/021 from/16/11/2018 to/20/11/2018` and check-in by doing `checkin r/021` +** Test case: `checkout r/021` + +Expected: First booking is deleted. Guest Raichu is removed from the checked-in guest list as he does not have any other checked-in bookings, +and is added to the archived guest list. -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +* Checking out specified booking of a room that is not checked-in +** Prerequisites: There is a booking in the room you want to check out. Add a booking to an empty room by doing: + +`add n/Pichu p/81726354 e/pika@chu r/022 from/16/11/2018 to/20/11/2018` +** Test case: `checkout r/022 from/16/11/18` + +Expected: Booking is deleted. Guest Pichu is not added into checked-in guest list nor archived guest list. -[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 +* Refer to the user guide to test more edge cases -|`* * *` |user |add a new person | -|`* * *` |user |delete a person |remove entries that I no longer need +[NOTE] +These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +=== Reassign -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +First, ensure that you have signed in by doing `login user/admin pw/passw0rd`. -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +* Reassigning a booking to another room +** Prerequisites: There is a booking in the room you want to reassign. Room 001 with its preloaded bookings and expenses is a good example to test this. +** Test case: `reassign r/001 from/16/11/18 nr/030` + +Expected: Booking in room 001 that starts from 16/11/18 is reassigned to room 030 (which has no bookings prior). Expenses from room 001 are +also ported over. -_{More to be added}_ +* Incorrect commands to try: +** `reassign r/001 from/32/11/18 nr/002` (invalid command, no such date) +** `reassign r/005 from/12/11/18 nr/101` (invalid command, no such new room) +** `reassign r/006 from/1/1/18 nr/007` (room 006 has no such booking) +** `reassign r/006 from/9/11/18 nr/007` (cannot reassign booking, because room 006 booking is expired) -[appendix] -== Use Cases +* Refer to the user guide to test more edge cases -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +[NOTE] +These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. -[discrete] -=== Use case: Delete person +=== Login/ logout -*MSS* +`clear` + +Unable to clear because user is not signed in. -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 -+ -Use case ends. +`login user/peanuts pw/peanut0` + +Invalid login account. -*Extensions* +`login user/admin pw/passw0rD` + +Invalid password. Password is case-sensitive. -[none] -* 2a. The list is empty. -+ -Use case ends. +`login user/admin pw/passw0rd` + +Successful login. -* 3a. The given index is invalid. -+ -[none] -** 3a1. AddressBook shows an error message. -+ -Use case resumes at step 2. +`clear` + +Able to clear Concierge. -_{More to be added}_ +`logout` + +Sign out of Concierge. -[appendix] -== Non Functional Requirements +`clear` + +Unable to clear because user is not signed in. -. 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. +`undo` + +Unable to undo because the revision history was erased. -_{More to be added}_ +=== List -[appendix] -== Glossary +`list -cg` + +This is the list of guests currently checked-in. -[[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X +`login user/admin pw/passw0rd` + +Sign in to Concierge. -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +`add n/Pikachu p/81726354 e/pi@ka.chu r/059 from/16/11/2018 to/20/11/2018` + +Add a booking to Concierge. -[appendix] -== Product Survey +`checkin r/059` + +Checks in Pikachu to Concierge. -*Product Name* +`list -cg` + +Pikachu is added to the list of checked-in guests. -Author: ... +`list -r` + +This is the list of rooms and bookings. The room view is the default view of +Concierge(TM). -Pros: +`list -g` + +This is the view of archived guests. It contains people who have ever checked + out of Concierge. -* ... -* ... +`checkout r/059` + +Checks out Pikachu from Concierge. -Cons: +`list -g` + +Pikachu is added to the list of archived guests. -* ... -* ... +=== Find -[appendix] -== Instructions for Manual Testing +`find -g n/Alex Yu` + +Find guest(s) with "Alex" or "Yu" or both in their names. -Given below are instructions to test the app manually. +`find -g n/Alex t/VIP` + +Find guest(s) named Alex with tag "VIP". + +`find -cg p/81027115` + +Find checked-in guest(s) with phone number "81027115". + +`find -cg t/VIP` + +Find checked-in guest(s) with tag "VIP". + +`find -r r/085` + +Find room 085 + +`find -r c/2` + +Find all rooms with a capacity of 2. + +`find -r c/5 -nb from/ 01/11/2018 to/ 05/11/2018` + +Find all rooms with a capacity of 5, without any bookings from the date range 01/11/2018 to 05/11/2018. + +`find -r -hb` + +Find all rooms with bookings. + +`find -r -hb t/RoomService` + +Find all rooms with bookings with tag "RoomService". [NOTE] These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. +=== Service +This section assumes that the default `Concierge` is being used. + +`select 1` + +The room details panel should be displayed on the right. + +`service r/001 no/RS01` + +Unable to execute as user is not signed in (assuming user did not already sign in). + +`login user/admin pw/passw0rd` + +Successful login. + +`service r/001 no/RS01` + +Expense was successfully added to the room. Notice that the right panel shows the +updated expenses. + +`undo` + +Previously added expense was removed, as seen in the right panel. + +`redo` + +Expense is added back. + +`service r/001 no/RS02 c/123.45` + +Expense was successfully added to the room. Note that the cost is 123.45. + +`service r/001 no/XX01 c/-5.00` + +Expense was successfully added to the room. Note that the total cost decreased. + +`service r/001 no/abcde` + +No such item in the menu, command cannot be executed. + +`service r/001 no/RS03 c/123` + +Money not in correct format, command cannot be executed. There are many different +failing conditions for the money format, they will not all be listed here. + +`service r/002 no/RS01` + +No guests in room 002, command cannot be executed. + +`service r/999 no/RS01` + +Invalid room number, command cannot be executed. + +`checkout r/001` + +Expenses are deleted in the right panel. + === Launch and Shutdown . Initial launch @@ -938,20 +1906,6 @@ These instructions only provide a starting point for testers to work on; testers _{ more test cases ... }_ -=== Deleting a person - -. Deleting a person while all persons are listed - -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. -.. Test case: `delete 1` + - Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. -.. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + - Expected: Similar to previous. - -_{ more test cases ... }_ - === Saving data . Dealing with missing/corrupted data files 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..d7459a067d98 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += Concierge(TM) v1.4 - User Guide :site-section: UserGuide :toc: :toc-title: @@ -12,19 +12,22 @@ 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-F11-2/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team Concierge(TM)` 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! +Concierge(TM) is created for *hotel managers and receptionists* of small hotels looking for a simple application to *assist +the user in hotel management*. More importantly, Concierge(TM) 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, Concierge(TM) can get your +hotel management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! == 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. +. Download the latest `concierge.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your Concierge(TM). . Double-click the file to start the app. The GUI should appear in a few seconds. + image::Ui.png[width="790"] @@ -33,13 +36,72 @@ image::Ui.png[width="790"] 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 +* *`list -r`* : lists all rooms +* **`checkin`**`r/001` : checks in the first booking of room 001 * *`exit`* : exits the app . Refer to <> for details of each command. + +[[Terminology]] +//tag::terminology[] +== Terminology +Some important things that you should know before using Concierge(TM) + +==== + +* Room numbers must be a *3-digit number from 001 to 100*. For example, +** 001, 050, 074, 100 are valid +** 000, 01, 99, 101 are invalid + +* The format used for dates is *d/M/y*, whereby +** *d* - Day can be 1 or 2 digits +** *M* - Month can be 1 or 2 digits +** *y* - Year can be 2 or 4 digits (if 2 digits, date is assumed to be in the current century) + +* A *Booking* ... +** is *Active* if the period between its start and end date includes today's date, inclusively. +** is *Upcoming* if its start date is strictly after today's date. +** is *Expired* if its end date is strictly before today's date. +** is *Outdated* if its start date is strictly before today's date. +** is *Overlapping* with another booking if its start date is strictly before the other's end date AND the other's start date is strictly before its end date +** can be uniquely identified by its start date. + +* Two *guests* are... +** *different* if they have different names +** *different* if they have the same name AND both their phone numbers and emails are different +** *same* if they have the same name AND either their phone number or email, or both, are the same + +* There are 2 guest lists: +** *Checked-in guest list* contains all the guests who are currently checked-in. +** *Archived guest list* contains all guests who have ever stayed in the hotel before. +** Therefore, it is possible for a guest who has made a booking to be in +*** neither lists - guest has yet to check-in and has never stayed in the hotel before +*** checked-in guest list only - guest has checked-in and has never stayed in the hotel before +*** archived guest list only - guest has not checked-in and has stayed in the hotel before +*** both lists - guest has checked-in and has stayed in the hotel before + +==== + +[cols="^.^1,<.^11a", frame=none] +|=== +|image:icon-key.png[Login requirement,45,45,pdfwidth=50%,scaledwidth=50%] +|This is a `login` icon. If a command requires login before execution, this icon will be located on its right in its title. +|=== + +[cols="^.^1,<.^11a", frame=none] +|=== +|image:icon-important.png[Important info] +|This is a *important* icon. You should read the information in these before executing the command, as it will likely +affect what you can or cannot do. +|=== + +[TIP] +This is a *tip* icon. You may want to read this if you are new to Concierge(TM). + +//end::terminology[] + + [[Features]] == Features @@ -52,144 +114,396 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. * Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. ==== -=== Viewing help : `help` +//tag::add[] +=== Adding a booking: `add` image:icon-key.png[width="32"] -Format: `help` +Adds a booking associated with a guest, room and booking period. + +Format: `add n/NAME p/PHONE_NUMBER e/EMAIL [t/TAG] r/ROOM_NUMBER from/START_DATE to/END_DATE` -=== Adding a person: `add` +[NOTE] +A valid booking cannot clash with an existing booking. It must also have a + start date from today onwards (i.e. not outdated). -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +**** +* A guest can have any number of tags (including 0) +* A guest can make an unlimited number of bookings with the hotel. +* When adding a booking, the guest will *not* be added to the archived guest list or checked-in guest list. +Their personal information will be stored under their booking in the room. +**** -[TIP] -A person can have any number of tags (including 0) +Example: `add n/John Smith p/98765432 e/johnsmith@gmail.com t/VIP r/085 +from/17/12/18 to/19/12/18` -Examples: +Add a guest "John Smith" to room 085 for the period of stay from 17/12/18 to +19/12/18. + +Adding an inactive booking: you can view the booking by selecting the +relevant room, under "All other bookings". Only active bookings (i.e. start +date is today) can be seen on the left room pane. + +image::AddCommand-userguide-notactive.png[width="800"] +// end::add[] + +//tag::checkin[] +=== Check-in room: `checkin` image:icon-key.png[width="32"] + +Checks in the first booking of a room. The guest who made the booking will be added to +the checked-in guest list. + +**** +* If the guest's name is not found in any of the entries in the checked-in guest list, then a new entry will be created. +* Suppose there is already an entry with the same name as the guest in the checked-in guest list. +** If both the guest's phone number and email are the same as the ones found in the entry, or only one of them is different, +then he/she is *treated as the same guest as the one in the entry, and thus the entry will not be updated*. +** If both the guest's phone number and email are different from the ones found in the entry, +then he/she is *treated as a different guest from the one in the entry, and a new entry will be created*. +**** + +[cols="^.^1,<.^11a", frame=none] +|=== +|image:icon-important.png[Important info] +|Expired and upcoming bookings *cannot* be checked-in. +|=== + +Format: `checkin r/ROOM_NUMBER` -* `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` -=== Listing all persons : `list` +Examples: -Shows a list of all persons in the address book. + -Format: `list` +* `checkin r/085` + +Checks in room 085, marks room 085's current booking as checked-in, and adds the guest who made the booking +to the checked-in guest list. +//end::checkin[] -=== Editing a person : `edit` +//tag::checkout[] +=== Checkout room: `checkout` image:icon-key.png[width="32"] -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Checks out the room's first (i.e. earliest) booking, or its booking with the specified start date. +Also updates the checked-in guest list and archived guest list with the guest who made the booking. **** -* 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, ... -* 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. +* If the guest does not have any other checked-in bookings, then he/she will be removed from the checked-in +guest list. Otherwise, he/she will *not* be removed from the checked-in guest list. +* If the guest has checked-in, he/she will be added to the archived guest list. +** If the guest's name is not found in any of the entries in the archived guest list, then a new entry will be created. +** Suppose there is already an entry with the same name as the guest in the archived guest list. +*** If both the guest's phone number and email are the same as the ones found in the entry, or only one of them is different, +then he/she is *treated as the same guest as the one in the entry, and thus the entry will not be updated*. +*** If both the guest's phone number and email are different from the ones found in the entry, +then he/she is *treated as a different guest from the one in the entry, and a new entry will be created*. **** +[TIP] +`checkout` 's intended use is the deletion of any booking. + This means that you can delete a non-checked-in booking using `checkout`. + +Format: `checkout r/ROOM_NUMBER [from/START_DATE]` + 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. +* `checkout r/085` + +Checks out room 085 and the room's first booking. +* `checkout r/085 from/01/11/18` +Checks out the booking with start date 01/11/18 from room 085. +//end::checkout[] -=== Locating persons by name: `find` +//tag::reassign[] +=== Reassigning a guest to another room : `reassign` image:icon-key.png[width="32"] -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Reassigns a booking from one room to another. The room's expenses will also be ported over to the new room. + +[cols="^.^1,<.^11a", frame=none] +|=== +|image:icon-important.png[role="center"] +| You can reassign a booking only if: + +* The new room is different from the original. +* Neither the booking nor any of the new room's bookings are expired. +* The booking does not overlap with any of the new room's bookings. +* If the booking ends the same day that the new room's first booking starts, the new room's first booking cannot be checked-in. +* If the booking starts the same day that the new room's first booking ends, the booking cannot be checked-in. +|=== + + +Format: `reassign r/ROOM_NUMBER from/START_DATE nr/NEW_ROOM_NUMBER` + +Examples: + +* `reassign r/085 from/01/11/18 nr/086` + +Reassigns the booking with start date 01/11/18 in room 085 to room 086. +//end::reassign[] + +//tag::find[] +=== Locating guests and rooms : `find` + +Finds guests or rooms, depending on the input flag and the keyword filters. + +Format: `find FLAG FILTER [MOREFILTERS]` **** * 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` +* Guests names and tags matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +* Any number of filters can be chained and used together **** -Examples: +Filters for Guest / Checked-in Guests (-g/-cg): -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `n/` - Name +* `p/` - Phone Number +* `e/` - Email Address +* `t/` - Tags -=== Deleting a person : `delete` +Filters for Rooms (-r): -Deletes the specified person from the address book. + -Format: `delete INDEX` +* `r/` - Room Number +* `c/` - Capacity +* `t/` - Room Tags +* `n/` - Guest name within bookings -**** -* 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, ... -**** +* `-hb` - Has Bookings Flag* +* `-nb` - No Bookings Flag* +* `from/` - Booking Start Date^ +* `to/` - Booking End Date^ + +[cols="^.^1,<.^11a", frame=none] +|=== +|image:icon-important.png[Important info] +| +Flags marked with * cannot be used together in the same command. + +These flags can be used independently, or with flags marked with ^ . +|=== + +Examples: + +======= +* `find -g n/Alex Yu` + +Find guest(s) with "Alex" or "Yu" or both in their names. +* `find -g n/Alex t/VIP` + +Find guest(s) named Alex with tag "VIP". +* `find -cg p/81027115` + +Find checked-in guest(s) with phone number "81027115". +* `find -cg t/VIP` + +Find checked-in guest(s) with tag "VIP". +* `find -r r/085` + +Find room 085 +* `find -r c/2` + +Find all rooms with a capacity of 2. +* `find -r c/5 -nb from/ 01/11/2018 to/ 05/11/2018` + +Find all rooms with a capacity of 5, without any bookings from the date range 01/11/2018 to 05/11/2018. +* `find -r -hb` + +Find all rooms with bookings. +* `find -r -hb t/RoomService` + +Find all rooms with bookings with tag "RoomService". +======= + +//end::find[] + +// tag::list[] +=== Listing all guests : `list` + +Shows the entire list of rooms, checked-in guests, or archived guests, depending on the input flag + +Format: `list FLAG` 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. +* `list -r` + +List all rooms +* `list -g` + +List all archived guests +* `list -cg` + +List all checked-in guests +// end::list[] + +// tag::loginlogout[] +=== Login : `login` + +Logs in to the Concierge(TM) application. + +Format: `login user/USERNAME pw/PASSWORD` + +Note: The username and password are both case-sensitive. + +[TIP] +The default account can be accessed with `login user/admin pw/passw0rd` + +A login allows the user to access the commands which can affect the +bookings. + +Commands which require login: `add`, `checkin`, `checkout`, +`reassign`, `service` and `clear`. + +Example: `login user/admin pw/passw0rd` + +`clear` + +`"This command requires you to sign in."` + +Attempting to clear Concierge(TM) without a login is not allowed. + +`login user/admin pw/passw0rd` + +`"Successfully signed in as: admin"` + +Login with a valid account, such as the default one provided. + +After signing in, the `clear` command can now be executed. + +==== Adding a new account + + Currently, Concierge(TM) does not have a feature for users to add an account via the app. Nevertheless, for the adventurous users who want to do so, this sub-section will be useful. + +Step 1: Concierge(TM) uses SHA-256 password hashing. This hash for your +*password* can be generated using this +https://passwordsgenerator.net/sha256-hash-generator/[tool]. + +[NOTE] + Concierge(TM) is designed to work for alphanumeric usernames and passwords in + mind. Do not enter symbols (!, @, %...). Do not begin or end your passwords + with whitespaces. + +Step 2: Add the entry to the `passwords.json` file. This should be in the +same location as `concierge.jar`. Note that entries are separated with a +comma. + +Format: `"username" : "hashedPassword"` + +In the image below, a new account with username "newUser" and password +"mySecurePassw0rd" has been added. + +image::LogInCommand-addpasswordentry.png[width="900"] + +Step 3: Close and reload the Concierge(TM) application, and your new account +will be recognised. + +[NOTE] + The parsing of the `passwords.json` file is delicate. Currently, if you + enter a valid json file format but an incorrect password reference list + format, you will end up with no default account. To resolve this, delete the + `passwords.json` file and re-run Concierge(TM). + +=== Logout : `logout` + +Logs out of the Concierge(TM) application. + +Format: `logout` + +* The special classes of commands (as documented in <>) can no longer be executed. +* The `logout` command will erase the command history, so users cannot undo/ +redo commands executed before the logout. +** Even if you login again, you cannot + undo your previous actions. +** This achieves the same effect as closing and re-opening Concierge(TM) +after a logout. -=== Selecting a person : `select` +Example: `logout` -Selects the person identified by the index number used in the displayed person list. + +// end::loginlogout[] + +=== Selecting a guest : `select` + +Selects the guest or room identified by the index number used in the displayed list. + Format: `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. +* Selects the guest or room and displays its information in the detailed panel on the right. +* The index refers to the index number shown in the displayed list. * The index *must be a positive integer* `1, 2, 3, ...` **** Examples: -* `list` + +* `list -g` + `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. +Selects the 2nd guest in the displayed list. +* `list -r` + +`select 100` + +Selects the 100th room in the displayed list. + +// tag::service[] +=== Provide Room Service: `service` image:icon-key.png[width="32"] + +Charges a room service to a room + +Format: `service r/ROOM_NUMBER no/ITEM_NUMBER [c/COST]` + +`ITEM_NUMBER` refers to the number in the menu given to each type of service offered by the hotel. +The default Menu that comes with Concierge(TM) consists of the following items: + +* *RS01* -- Room service: Red wine -- $50.00 +* *RS02* -- Room service: Beef steak -- $70.00 +* *RS03* -- Room service: Thai massage -- $100.00 +* *MB01* -- Minibar: Coca cola -- $3.00 +* *MB02* -- Minibar: Sprite -- $3.00 +* *MB03* -- Minibar: Tiger beer -- $6.00 +* *MB04* -- Minibar: Mineral water -- $3.00 +* *SP01* -- Swimming pool: Entry -- $5.00 +* *XX01* -- Adjustment: Discount -- $0.00 +* *XX02* -- Adjustment: Typo -- $0.00 + +[cols="^.^1,<.^11a", frame=none] +|=== +|image:icon-important.png[role="center"] +| +* The cost can be specified if the guest is to be charged an amount that is + different from the cost in the menu. Note that the cost has to follow a strict format + such as 100.00, i.e. with two decimal places. The dollar part of the cost should also + not exceed `Integer.MAX_VALUE`. +* Only occupied rooms (i.e. rooms with checked-in guests) can have expenses charged to it. +* Items in the Menu may be modified, added, or removed through `concierge.xml`. + Only item numbers from the Menu (case-sensitive) may be used in the `service` command. + Future versions will allow displaying of the Menu on the main app. +|=== + +[TIP] +Negative values can be used in `service` command. This can be used in cases such as when +a guest uses a voucher, hence allowing the total expenses to be reduced. Negative +values can also be charged if the user wants to remove or edit an existing expense. +The two expense types, XX01 and XX02, were thus created for the purpose of adjustments. + +Examples: + +* `service r/085 no/RS01` + +Adds an expenditure of the item *RS01* to the room's expenses. + +* `service r/096 no/RS03 c/12.34` + +Adds an expenditure of the item *RS03* to the room's expenses and charge $12.34 for it. +// end::service[] === Listing entered commands : `history` 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. -==== +[cols="^.^1,<.^11a", frame=none] +|=== +|image:icon-important.png[Important info] +|Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous +and next input respectively in the command box. +|=== // tag::undoredo[] === Undoing previous command : `undo` -Restores the address book to the state before the previous _undoable_ command was executed. + +Restores the Concierge(TM) application to the state before the previous +_undoable_ command was executed. + Format: `undo` -[NOTE] -==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). -==== +[cols="^.^1,<.^11a", frame=none] +|=== +|image:icon-important.png[Important info] +|Undoable commands: those commands that modify Concierge(TM)'s content +(`add`, `checkin`, `checkout`, `reassign`, `service` and `clear`). +|=== Examples: -* `delete 1` + +* `checkin r/001` + `list` + -`undo` (reverses the `delete 1` command) + +`undo` (reverses the `checkin r/001` command) + * `select 1` + `list` + `undo` + The `undo` command fails as there are no undoable commands executed previously. -* `delete 1` + +* `checkin r/001` + `clear` + `undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +`undo` (reverses the `checkin r/001` command) + === Redoing the previously undone command : `redo` @@ -198,27 +512,99 @@ Format: `redo` Examples: -* `delete 1` + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +* `checkin r/001` + +`undo` (reverses the `checkin r/001` command) + +`redo` (reapplies the `checkin r/001` command) + -* `delete 1` + +* `checkin r/001` + `redo` + The `redo` command fails as there are no `undo` commands executed previously. -* `delete 1` + +* `checkin r/001` + `clear` + `undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +`undo` (reverses the `checkin r/001` command) + +`redo` (reapplies the `checkin r/001` command) + `redo` (reapplies the `clear` command) + // end::undoredo[] -=== Clearing all entries : `clear` +// tag::clear[] +=== Clearing all entries : `clear` image:icon-key.png[width="32"] -Clears all entries from the address book. + +Clears all entries from the application. + Format: `clear` +The rooms are maintained - only all their current and future bookings are +cleared. I.e. all maintenance requests and faults will still be recorded. + +Example: `clear` + +image::ClearCommand-success.png[width="400"] +// end::clear[] + +// tag::autocomplete[] +=== Autocomplete: kbd:[Ctrl], kbd:[Alt] + +Auto-completes a partially typed in command by a user. + +**Function** Press kbd:[Alt] to quick-clear the `CommandBox` (saves time + for user when he wants to clear the box). + +The command box before kbd:[Alt] is pressed: + +image::servicepreclear.png[width="419"] + +The command box aft kbd:[Alt] is pressed: + +image::servicepostclear.png[width="371"] + +**Format**: Enter `COMMAND_WORD`, followed by kbd:[Ctrl] key. + +**Function**: Press kbd:[Ctrl] key again to proceed to the next prefix. + +Example: + +**Step 1**: User enters `a` in `CommandBox`. + +image::add.png[width="579"] + +**Step 2**: After kbd:[Ctrl] has been pressed, it automatically inserts the + command and the first parameter `PREFIX_NAME` in the command line. + +image::addPREFIX_NAME.png[width="581"] + +**Step 3**: After filling up the name field, e.g. with Anthony, then press + kbd:[Space]. Finally, to insert the next parameter `PREFIX_PHONE` + press kbd:[Ctrl]. + +image::afteranthonyspace.png[width="581"] + +**Step 4**: Repeat Step 3 until all the parameters are input, then press + kbd:[Enter] to execute the command. + +image::fulladdautocomplete.png[width="953"] + +**Note(s)**: + +* For finishing entering a prefix field e.g. 'n/John Doe', user +has to insert kbd:[Space] on his/her own before pressing kbd:[Ctrl] +again, to autocomplete the next prefix. + + +* For commands such as `checkin` and `checkout`, user has to specify +up till at least `checki` or `checko` because the application +is unable to distinguish from the two commands without sufficient +information. The subsequent prefixes will follow suit accordingly. +// end::autocomplete[] + +=== Viewing help : `help` + +Format: `help` + +**** +* Only one help session can be in place at each time. +* Your help session resets each time you close the window. +* Your help session is retained if the window is not closed. +**** + === Exiting the program : `exit` Exits the program. + @@ -226,35 +612,54 @@ Format: `exit` === Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +Concierge(TM) 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]` +== Command Summary -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +* *Add a Guest and assign a Room* : `add n/NAME p/PHONE_NUMBER e/EMAIL +t/TAG r/ROOM_NUMBER from/START_DATE to/END_DATE` +* *Checkin* : `checkin r/ROOM_NUMBER` +* *Checkout* : `checkout r/ROOM_NUMBER [from/START_DATE]` +* *Reassign* : `reassign r/ROOM_NUMBER from/START_DATE nr/NEW_ROOM_NUMBER` +* *Find* : `find FLAG FILTER [MOREFILTERS]` +* *List* : `list FLAG` +* *Login* : `login user/USERNAME pw/PASSWORD` +* *Logout* : `logout` +* *Select* : `select INDEX` +* *Room Service* : `service r/ROOM_NUMBER no/ITEM_NUMBER [c/COST]` +* *History* : `history` +* *Undo* : `undo` +* *Redo* : `redo` +* *Clear* : `clear` +* *Help* : `help` +* *Exit* : `exit` + +== Features planned for v2.0 +* Commands +** `addx` - Add a guest to the archived guest list without needing to add a booking +** `menu` - Display the default Concierge(TM) menu +** `maintenance` - Tag a room as under maintenance +** `export` - Export the logs of the current session to a text-editable file +// tag::archive[] +** `archive` - Opens the log where all user input is captured and saved. All keystrokes are +captured, including invalid/mistyped and login commands. +// end::archive[] + +* Other features +** Displaying the checked-in guest's room's information when selecting the checked-in guests == FAQ *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Concierge(TM) folder. -== Command Summary +*Q*: What platform is this application available on? + +*A*: This application is cross-platform, and can be used on both Windows and Mac OS. -* *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` +*Q*: Where can I go to purchase this application? + +*A*: This application is 100% free-of-charge, and is open-source for any and all to contribute to make it better. + +*Q*: I've found a bug in the application! How can I report it? + +*A*: Please open an issue in the issue section and we will see to it as soon as possible. Bug reports only serve to +make our application better, so do not hesitate to report them to us! diff --git a/docs/UsingCheckstyle.adoc b/docs/UsingCheckstyle.adoc index 5e2f02bd279c..f034e828064b 100644 --- a/docs/UsingCheckstyle.adoc +++ b/docs/UsingCheckstyle.adoc @@ -20,7 +20,7 @@ Restart the IDE to complete the installation. + image::checkstyle-idea-scan-scope.png[width="500"] . Click the plus sign under `Configuration File` -. Enter an arbitrary description e.g. addressbook +. Enter an arbitrary description e.g. concierge . Select `Use a local Checkstyle file` . Use the checkstyle configuration file found at `config/checkstyle/checkstyle.xml` . Click `Next` > `Finish` diff --git a/docs/UsingGradle.adoc b/docs/UsingGradle.adoc index d1be2f3b7c3a..a7eeaaf87d03 100644 --- a/docs/UsingGradle.adoc +++ b/docs/UsingGradle.adoc @@ -43,7 +43,7 @@ When running a Gradle task, Gradle will try to figure out if the task needs runn == Creating the JAR file * *`shadowJar`* + -Creates the `addressbook.jar` file in the `build/jar` folder, _if the current file is outdated_. + +Creates the `concierge.jar` file in the `build/jar` folder, _if the current file is outdated_. + e.g. `./gradlew shadowJar` **** diff --git a/docs/diagrams/AddCommand-activity-diagram.xml b/docs/diagrams/AddCommand-activity-diagram.xml new file mode 100644 index 000000000000..c4a0c092e48e --- /dev/null +++ b/docs/diagrams/AddCommand-activity-diagram.xml @@ -0,0 +1 @@ +7VvLkqM2FP0aL9uFJB5i2W73TBaTZCozVcksZZBtqjFyAPcjXx/JSIBA2GBjtzPpWXjg6ol07tHRlXqCHjavn1OyXf/KQhpPoBW+TtB8AqGHXf4rDG+FAWGvMKzSKCxMoDJ8i/6h0mhJ6y4KaaZlzBmL82irGwOWJDTINRtJU/aiZ1uyWG91S1a0ZfgWkLht/TMK83VhxY5V2X+h0WqtWgaWTFmQ4GmVsl0i25tAtNz/K5I3RNUl82drErKXmgk9TtBDylhePG1eH2gshlYNW1HuU0dq2e+UJnmvAhYuijyTeEdVn92YF54tRP/yNzkm7t870anZkiX5XbafsXueAcAtn/RZlc6fVvL/WOU/q6IvbBUFqjb+JUWFeiPcvKhsUGsP7qeDig+2ePLLOsrpty0JROoLRy+3rfNNzN+A6FUUxw8sZum+LAoJxcuA27M8ZU+0luIGmC6WPIXE0SrhtpguRfPPNM0jjqR7ac6ZaCHjDUbJ6rt4mcPK8GVfaI7leEhPAFC+19pzfM/xH8vvq0+xnHXRMn2tmeSUf6ZsQ/P0jWdR/gmLEtI7FaxfKqg7QOJiXYM5QjIjke61KmuuIMYfJMq6EOd1Ii6Mno1Q4Z+V38lxFliRQ7337GFo5MOenIVGSXUl7IoKW2jcf8kF8DgH/vzRM+HRdz1E3L54bIKtLz7lVwADPj0buWAcfKrVQuITILsFUO4PbYAC1zkfoK7dzYgxC54EIKgRQxuSriIJUYvjR/+1LQkpa8HSkKZFvoQltLBuSRjyEa+KGwEOjgC8rwulxagNasKZLRh7En2EFuOzGRO+HDvzLthX1Fwbtg6foEl4L9Zt/hbEJMuioOEGvI+fyCaKBSS+kzXbkAaKRS76GuV/yRLi+Yfwsakj3hKOgr/2r175/kO6YMPHljiggZHzF9ixHVEiJgsaz8q1XmUpJ7PDAzK2SwP5xa7k0pyDhspsWPIyDTV50uknd9bUBRjrzmIgc6vtKsqW0pjk0bOue0zuIzvxlUX79Vz2wLbgFDsesGHx6+t98a1pmSR+Pb3+YjxklXWV0mgFObZWL7Qbfl4M4oGKVEa2XGZUy8NhR95qObaiRNb9xZxo9b4ATWHxh6LGimvKeetHP4ota/TjzLgi3OzZIt87rNCXe8cjG8HmySLbFr58WdNlnLZyTfh+rll3Q88e7IaOrS9Zd9d1Q4g8IygHexpwnCmybQw9B7vQcv1GvcibImBVOfC7+SFA6LJ+CFt+eB+G3DBTa2CnrAPHZR1JA+kDtgnmyyU0wzx0F67j9vYtpQUDDnehOMYQZ9BqqDPD9kEtZNruYYTNg4sM2qzJStxrlU5lab5mK5aQ+LGy1uehqY4XLM850woWKrmNbWlSWOTQ4kvMTBcEavTYYsd+QgO2GU6Jj6MMd648cHRxgmADBB2EcYq/uj2gEcfRNmtAIFuTrUjPeF/4Bofk9CSH1N3/KAjwOM6ILEcbYLfti8iw7Izhi55lEio05uPrzC+lFaTAl74gJT444ibDJ/NC+l7FXQYIC9v39BkG7lWFhYN1Ba/3xfY19Y/RaaoD2jpNlNgaQVecQCVqmg5RScoRuthlPQI4g5YDGbQZjtgxFnbUMQs1qEED1JqcfhKZtIMu39eCLlTkJTVGNfqGLv7g26ffWP5JOPPja0C3ecSSiyq44xuV91NwfmOeodeaZ2AbJtpG5080bvuWnGjr9yKkJPX1xzRBrEdTylVbmydkmKcxlLZhdf8/cqB1PQ7EfTY3o8ioFlorXQW7hZUmq/Yqa7g39ZJOajE4STpdRxcdi5O4cFqmYb95LNFbF+m1ItAv7Hl2KAWPG0rBpjPmawPb6wQ2uBqw1eJ3w8C+gzoW0I1jtxWOHxu7/hWwiw9Dt4nc4Svj2UEkz4BcoGIPtwJdf6pvlRE+MQre3I+iZjh9vLCV32fRPxS2EmHJEYJWvaTuSEGrpshChgjypUSWbzrd//l1bYlgFbkxnE9dbMgNFHq1QOHkwE2A2wgU+ga1q2L2QwKF0NIPya98BIlUn5vHQoMP+xsVlVuwdzhkbC4EAI6rLsp7ox+XjQ5dNor4FFlsl4d8nQv/IzeOhm8hDjHThXgGWGAw0SDP1bcIV+YZvvM+cOMIWlOoHUmcRkK202CzEU8khpKQA1rfODIJta8cqZjsA9tsyEfMnAPCbQgojNVuox6ONV2bHiNsDqweuvW996EDKM4Qc+lFYiVWb3kjijzrwKEp30tP64m2fRpFldGkgXcrzqUfNDr9OMehPd6WrI7xWzppQFfckQGrz2WZnz+qVUKvziYqInMjZAI9X1M0zVi9P9IVjMPNIOCbVNf48bAyMPFxxa8MXhRJtu9qmwzs+fL9K00jPsSijQHXAl0D9PtefD53gWxGo+DQ+/zlGtioqBkr6ID/cWDy1+qvMIvs1V+6osd/AQ== diff --git a/docs/diagrams/AddCommand-class-diagram.xml b/docs/diagrams/AddCommand-class-diagram.xml new file mode 100644 index 000000000000..f18d3d36fa55 --- /dev/null +++ b/docs/diagrams/AddCommand-class-diagram.xml @@ -0,0 +1 @@ +7Vpdc9soFP01ntnuTDySkPzxGDtN9yHdzTTd2fYRS8RmgoSLcJ301+9FAn2BGyVRvJmuMx5HXJCAew9w7pFHaJnefxB4u/nIE8JGgZfcj9DFKAjmQQTfyvBQGsJIG9aCJqXJrw039AfRRk9bdzQheauh5JxJum0bY55lJJYtGxaC79vNbjlr97rFa2IZbmLMbOs/NJGb0jqLvNr+B6HrjenZ93TNCsd3a8F3me5vFKDb4q+sTrF5lm6fb3DC9w0Tej9CS8G5LK/S+yVhyrXGbeV9lwdqq3ELksk+N0zKG75jttNT/zuj33bkE+fpFc2lHqV8MJ7J9zRlOIPS4pZn8kbX+FDGjK4zuI6hbyLA8J0IScGp57oipUmiWi/iDWXJFX7gOzXIXILTTGmx4YL+gCdjph8L1UJqiMB4my1u1J1g9sAqSA5trs3M/Y7pI75vNbzCudSGmDOGtzldVTNJsVjTbMGl5KluZOZ9SRlbcsZF4Q4TXO2McpB+oMuXOKVMrYDPeMNTrIYu02paWxzTbP2Zb00PpaHV663VWxDHhU8EvyONmmSymkTKOTqe4HlyfxATfoU0WMCEp0SKB2hibjDg1Is3mGmU7OulEBjAb5rLINRGrJffunp2DUG40Ch0I3JqIfIMin+tciJgVhiCVOJyglPw3IIpzCi0Vpa1LNzgUYXCDDMnjME70vJjxktcN5yuTQbajNzKnwFbx/CqaHYR1pZP2kvKxOH2W1as+g3cSDIFSi6xxCUCVeC3HIZfeDFawAf8uvTG0SiCsS+h7Ndl+KjmQi55BtPBtAgyAXjviYL4ASR28dpA5n5DJbmBcavKPezvXbSGB9DaC3sTN/YM1npCLQxfjrSZhTQLJowW4S9hYk4CfxCMWAtfgwbZoEEOgDC8Iuya51RSrh4vyrYd4BwTGxUUgmGgMPX6QWH2ciTMnXvOmki1sfymvv7cpSsi3sEmU2w4KwFXeqNRew1OkgXnd+CFRmu1VANP29+dtqAhtyDXgRkOhLtZP9xNgpcDz0eP70EkS84VpVXMiuE8p3HbV/2cS+6p/KLvUNdflRMhfFDKYNBftE+LQlkXRJU/SWLR5Q6ZgBHznYhJ6wwH4gZrSLeK3E5veDlyONnYBGFY0u/tUbg8r3u4VuBtUBqvTWnCeSd45ej1XU2+3HlQ6LcfhFCH8pRzth5UAKGadj9shBY2qmm82jYi+fZX2UMGYMIo6kTbm1t7Q+hArWExL9oaIsfWUJ4+EIysBYLJtx03Pjrb64GcQ5OMixQSqapBdWpNF3il3Ay58/TCdaqVFhhk2VlpLnn2m8oIw+AtZ4RPAux/kuyhGXoU4v7Elex5Q5x/tv4A86tZU64klery/0ugrPSrX+QPnLnmEAz6RXoIhu3baf3xk60z3wrv0bMtvfZnA8XQkSQ5YzhECF35cif5OSU7L0hLHgu1nZe4l+sA0ohRABuxhunGlAC5fWME4G1Lwi/Oet+EAuxPQgt7TuI7G0AANsPtqDHd9xKegMsTKWiSAhd+eqGh8eqpryjrTHuG2HmCE1HoEoXXimvkUFidq3qAsLqErpNy+iwy8WpwcAifzmU+QOKHbIJhhXkg4bOSN2u186sOlBFFvXHoh6NaGPXHXmhE02siKExPsZfGS66nKqIG/01J9IAMfRxFNOi+5I06R3dvRdTzxsE89P3pJJx60WzWeixC83FhDVCkWnWgM5xaiuxzw4ThpJUeQyuttoUKUMfTSpF9uujD45SsjH4ltTKadwT50KFWzhwomwyBMvuFjEpMPuxIkY+sy//QEfKVx89qCqPOEJ7UWqYpV21PFOcnPy3pLaCU28BT0hgnVob4FRPqoXcOzW9G9uvbw299n0djjF7fpDGHvH4cHjOddaQKhMbR85jMxENjTVMKMuO3zzM0Hc9rllMRjlegMrbQ+vuJyhyRykTT7iljvykJhqEyUKx/7Ftipf5BNXr/Lw== diff --git a/docs/diagrams/AddExpenseSequenceDiagram.pptx b/docs/diagrams/AddExpenseSequenceDiagram.pptx new file mode 100644 index 000000000000..f21bad12bbaf Binary files /dev/null and b/docs/diagrams/AddExpenseSequenceDiagram.pptx differ diff --git a/docs/diagrams/CheckInCommand-activity-diagram/CheckInCommand-activity-diagram.xml b/docs/diagrams/CheckInCommand-activity-diagram/CheckInCommand-activity-diagram.xml new file mode 100644 index 000000000000..db2759fa2c11 --- /dev/null +++ b/docs/diagrams/CheckInCommand-activity-diagram/CheckInCommand-activity-diagram.xml @@ -0,0 +1 @@ +7Vxbk6o4EP411j7NFuGqj6PHmT1Vu1VbNVV7eYwQJTtIqBiPur9+EwgIITisCuiMvgx0rnzdX6fThBlZs/X+lcIk/I0EKBqZRrAfWd9Gpgls0+V/hOSQScaGmQlWFAey0lHwhv9FUmhI6RYHaFOpyAiJGE6qQp/EMfJZRQYpJbtqtSWJqqMmcIVqgjcfRnXpnzhgoZQCwzgW/ILwKpRDjx1ZsID++4qSbSzHG5nWMv1lxWuY9yXrb0IYkF1JZM1H1owSwrKr9X6GIoFtDlvW7qWhtJg3RTFr08Aam4sJWDgOAIvAWHpPsocfMNqi/BHSibJDDg7vgeuB30xDto64DPBL/iCJKN8wSNkbg0yUL3EUzUhEaNrQmrvWM58Pr8woeUelEtsx+I+XpNCh4Nhpho4o8ska+/I6ggsUTQus855iEqfDkpi9wDWOhPX9gWgAYyjF0tSAKe9LczAMOQcY4VXMZT7HEPHCaYApNzJMhHBDtkKF0zrQOXKIMrQviSTwr4isEaMHXkWWcqvJmkiS5DaxO1qcJUVhydZyGZQ2vio6PuqZX0hVN6i9hZYDzgh5SygLyYrEMJofpSVdGVVb+AcxdpBIwy0jXHTs4VdCElmPQ0cPf4n2Pzv57d+yu2w+YhKNli1FXCPURw3mLP0QN8oVks1svVIoiiDDP6rD6TCWTX8nmE+kUKaVu4Zcma6ipmwKspWiqWIa7ZTX8JAlbb4iMTXuRtZiBtwZ8dZsIzwhphtRtCDkHcermtar/NuFmKG3BKbw7rirr+oZUl9qmYNaY7vwer6W7culZNqN8PsaXLar6gcaMheyMptVMzmHzSa0Fr7pGNBfGi7wgqfJGWwu6fU0+igOnsUSK3pKUJxJJO7jk+puqbdcPxFaslPaOUn8PGooEb+JN4qTucA1tFVYnbBDuN/zXKxdB9bpCbY8Orxf3Dpfdmw1OrjesmN/DD4NyXqx3Xy8cjS4Vw2IzR7XVRZcr+5xxxqHO76Cwx3fsR06df56ffH3nv2eBjdwKafbAufcFvVsczjqAe+ObcjT2FBfi6f5yYDLgqgegGuBW5/kc8bDka/YF91K3qCSNUiTCOfZF9BsF8D4QgNrF7E5XruIje+54KFULREVNu3HcQ1DUXXW47nhIKjbAgvFttA05vsEUxRM8xyDG4l93ILyq5W4mu99lKT5tBqRvm764bJg2G7YBvSRfgCTm/AKZzJfs5+9mPmtkavvqXIOfY+hL8ZqJNEsRP779/gku9SiB9+uxDe7advdS7rvxrL3xc3Fq7Buj9XNKuya/azC6jjXXoXdu/a9Q+6pQX1Tnfte6XOfI4pgcGhwvSh4ON9hnK/jDul8W+RiBnx1egEZx3Uy2mYnztdWFOiZ3ThfdRx3rByGUOoDYJ1scPmeqUXieMhEXsGsHnIJ1j2vXKbmXIHV165B8/owXZGecDzKXvr/9HjV393y4zlV0hRs6GP5sW8j9j+PNVYXS0xr5O45/61Frq9I2bqxVWuirNJ9ZsDd2wj/zrMi26xbkdOXFeWD34oVAWMynBnZtxH8XP8Apq1Jp3a0iwAAVBUIujtyadcTtc9BMBIH1VEaZO1CMhLHuwMkHj5E5bjLYKLMz5IGWZCWN4vwhtVZ8AjMzlsXVHtoewZTzf2dtTC0cG8CyKTxQeW3DHCRVzf+LwDAtJQT5ZM6ArYGAPsaALQ4nHc5AG6DL+nhAZ02p5eKTyM+ou0myb5bWeK9oPqJTyIUSr+kv7aMcY3h8GpxYPDxKcmlisxzohXeOzXWd/UhiashhTNtzkEYWIhRdkBg5HyrWQTHgCnmUNFwrpeSOUhRDWuBKPZh9CwL1jgIoiZuVmOpK6xGQDmnY2nCyyKCKatFTYaepRbNZteZoohTTWD+4auJT6MFSzkYoUvWuF0pQbNv/pgbMH/rVI4YPzNR7EkLoujCtmvoyNNELQ//pWNOr/7L0xw0/JL+ywHD+S9Ps6X5kkpwvQGVoEn4O9NWaYeAIOGsYsJSl5WmGow0/dCci/jMHs1T1ajLD0y60qNmN/QlyQQM9euR7tjEb4//SiFL+x3/X4U1/w8= diff --git a/docs/diagrams/CheckinCommandSequenceDiagram.pptx b/docs/diagrams/CheckinCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..bed7543e9d57 Binary files /dev/null and b/docs/diagrams/CheckinCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/LogInCommand-LogInManager-classdiagram.xml b/docs/diagrams/LogInCommand-LogInManager-classdiagram.xml new file mode 100644 index 000000000000..99178c9a683c --- /dev/null +++ b/docs/diagrams/LogInCommand-LogInManager-classdiagram.xml @@ -0,0 +1 @@ +7Vrfc9o4EP5rmEkf0rEtbMMjkNy1N8k1U3pzvUeBBdbUtjhZFNK//la2ZCxLTtyDTF/oZBq0Wv3a79vVrsgILfLj7xzv0keWkGwUeMlxhO5GQTANQvhfCp5rwThUgi2nSS3yT4Il/UGU0FPSPU1IaSgKxjJBd6ZwzYqCrIUhw5yzg6m2YZm56g5viSVYrnFmS/+miUhr6ST0TvIPhG5TvbLvqZ4VXn/bcrYv1HqjAG2qf3V3jvVcSr9MccIOLRG6H6EFZ0zUn/LjgmTStNps9bjfenqbfXNSiCEDNFDfcbYnestRBmPnGwZTSBNnjFc90b97ua05nAklURRu2iKvFM/KelooJ7gtK2xnoOCj3bFSPQ2KtvL3guU5K0q9Lmy2XrruVXZqZg8q6xK5fw+6DykVZLnDa9l7ADKCLBV5Bi1f7oFm2aI5AUpCMknGam+Kdj6CNs7otoBGRjZ660u1pJymhAVosf3CYPq74CR4qNTvJiD5TrigwKCZmklI3XkpOPtGWjuYBCsURc255Chy7AXPbygBnkZYTgR/BhU1II4Vi5SXTabvFZyHE2n9aVzLUoOw2lmUo2ybyU9kgQ+KLz1ks7mjAsFPI9aFowMb8QG42GXOaRQjHNkA9qDxOoxd5GW7tV4Yj1HkN/BZWDkQ7YUvMNFDnoVdrOOhgZ1G/RzskAXdA9t+LB5xAaGNWwiWB5pnuCAOA2nDr8EQMLLP9OuUZskDfmZ7eYxSQJzUrXnKOP0B02LttdDNNR+CyNBYypGKSZzI6PKkMfA7okd8NBQfcCmUAIJahnclXTXHyDHf0mLOhGC5UnqFlPNoNp/PXKS8JEnGJkkiZJEEBS6SBBcgydhxN8hQvy8JL3Cu4vqnnaCsAOxU/F4KDi7VG77h3MIyWsFqbrUsrEQ/49fKjccnyWdlESliMHaTVZdtSpOEFJIYTGCBaxZIyHeMFqKyWDiHH7DhwoOYGsLGF9D2T234keocgkMBZ8G0QpgAxQ6kFA722FxAg7mgwAdXGAS+1jsHe1de0EEyoxVCNZI6TepeusNgzAGQjHQj9K1vYYtsbJEDxwyvSPbESiqpCTJe63bw/QUQhsEwCCfnIxj1eC8tl2B0knwsbt7VHryC1JrgYiSHIN+rTlqldKAHWqa7a/deyB3jMiXJEy7LA+NJu/+dc7JPe3Fj9lyDw6WYFdnpg5NZ0QUuhvj14MBTlq/25YAMfeAte4G7FEWdy9TOlpHDYugCFtOJXMtkvmUzUiQzWbzKbCoDp6Jr01TkSMXX1ud/JDGBcdAqYD9fFU+rRt3nx0gLnginsG2ZojmzmypkzpvyteNpvdYv2Z6viUELSN62pCk0ahlJjIK7FyLvfexNpwZKvk3s0IGSlnGSYUG/m0Z2Qaf28CS9ulVTmRQJ/E4krs+rBrUL68484aRTmyFzntpIL8yjFdlmUxJDp+JaY7ph9NO+cqKfjtofIIY/0FJYbLzm/L8s59fZV3/SH0xC2wWaV6izIlVgUeW2OlBNl89kQ+Boa1Jxprrx/yhZ8ScU/tfbvPc294dzoD/Xd2J+iVzft18DLCyvyf7/ANGR7TtBvEC27/cV6zhJ7uX9/0IavxuUwMN1dX/ckbU4ZfzOOa8J/huxyZHhO9k0vsQtYKf47YThL0HtZ95rwmAnDJN4PoucCUPni4CzEoZ4Epkps+Mt2Y8cVAkvQJXALm3qgCFfB25ejy3XCFETp+UyYwcn4sGk6M8gnBzQz7xnccCuL+BqKC1wX6twXUZxJdn9lfDLZWxfnRyEQ2tcfc8aRa7CZniR64X6gafxWAusNyxyO988jbsvHEOL3IlnTuT7nYnetMqF5umL8lr99McI6P4/ diff --git a/docs/diagrams/LogInCommand-sequencediagram.xml b/docs/diagrams/LogInCommand-sequencediagram.xml new file mode 100644 index 000000000000..582cc592676e --- /dev/null +++ b/docs/diagrams/LogInCommand-sequencediagram.xml @@ -0,0 +1 @@ +7V1db6O4Gv41lWYvijDGBi6TtN0daVYa7czR7rmkwU3QEMgCmbTn1x8bbIKNSYASklTTizYYY4j9vM/7aXoHF5vX31N/u/4zCUh0Z5nB6x18uLMsAIFJ/7CWt7IF26hsWKVhwDsdGr6F/yO8kV+32oUByaSOeZJEebiVG5dJHJNlLrX5aZrs5W4vSSTfdeuvSKPh29KPmq1/h0G+LltdZB7a/yDhai3uDEx+5tlf/lilyS7m97uz4EvxU57e+GIs3j9b+0GyrzXBxzu4SJMkLz9tXhckYnMrpq287qnlbPXcKYnzLhd4fF1++tGOiEfGEb12HoQ/2QPmb3xS8L879lTznLzm934UruI7OKM9IvLCmotpjvP7rFhMdgLg7WtxQlxJP6343+IOz9rxu47yJVmFSzEU/YbP6vC0rfgOotWSbmcVq0TYPJj09H4d5uTb1l+ys3uKatq2zjcRPQL0Y+Q/k2heLe4iiZKUnoqTmPafZ3ma/CCika45Xrrk+YV9mTCKau2BT9yXJf+SHPYA02M+nw98Mn+SNA8pHGe8OU/Y82T08cJ49Z0dPFiHhi/FRQ8uH7d2P5P+oEX17eu44FBhdyKvtSaOk99JsiF5+ka78LMeh+z+IBBYwH5dEwbk8kafC+GqGuoARPqBY7EFl7CBS84yE66ihx3o4+YqEhAg4pxexeI0f1Qw4qpatmnazjir6pjNZbWtcy2r67TSDfuOnfmgQQaLZLNJ4qwm+uV4F5B913qGWIMaihk3sIfKvoolDpK/+AKZTZQgEz+yVegCMN7Cz48AKxtztAgDoIky4GlQBiz4fpRZTZDBWaEu/vRjqrrTBh6oGt6yj7tN9CV8IVFYLO2WpCG9OWETGvHmr4e2U7ihxknu00vS6jiK/G0WPhd3ZSuWkuUuzcKf5C9SArxoTXY5u9Oism3MJpiQM3t4xMdVj4KYNtVQh+NIq18tLV99qFl9repwRuAYR8cx75F/Zeo9b7F4ejo99eNOaTWHb0JQmnOqo23bHmFKbdCc0kKiPscfRqLmyIHew3EzQCNRIy+zC0/zplY7j7LMGuIcU3IWcDYvLjsxxUen1O49p0ieUsoynSQHjqCJnFuY0f4gdeUZtTtykTcCRhHWUdFXP8v2SRr84WfrL2GW/6KjkXw+5zQdwXPREXI/ovAA00Ky9GisI530WGP4XzrhoThchiRdka9+mn0EVX7dxjEEjrT+OuNYQGR049g7r0hdxjaG2DNkmQJ2N5mqyOtdZlNjTskrxW5OPh3iFBH1PuMq7pl2CnRYLYHPXSGkT36wGW3I7Z4OuGVK1EyDw+nfGuAgcTBj8XV6tIxo/3Ap44EuYvr2D8OOQTHMj//LsdQzkNJfCEkghfVbAWMapiqFGhMG6RQbb0tJ5OeUm6Tb60DEn+FrEhbhLb1JClRuz5JduiT8IqsWvlfGoVxnIAfYllv+xo46rlGdY79t+Ta5T1k/P3Ib0TF5ecmI1KcQimpmu+nzhpxsmcJhoTs/Dn4Jiyws5DXM/xEd6eei3TBdix/XdO00wlWCRVIjPeSNYvOC8mbZnlETA/H8B21xVEy6SmPlB4lxkSLW08obaOZQhARkWz/uLAOt+bvjo+z5MrJx4iTd+JF2JH/DTIqoeVSITNUkd1fzfOWTKNnFVLTUvcP/5GHUeuGtG77Ymc09rfkFPfSIRCrpzE6jrTiNmsSh1sCtQsLvMcbA2E7jcV5VFsB15jM80y3AISt0esJh3xlHUJpwjZeuDXCNMd3ahF6rTGc5ZfFlF6E+SO+aSm3NOBhF8ep1q+O06NbLWrWl+pEUb10XC8R3Vcb39Es4yL6gNrbNhr+masrOKtc5OdTESrfpYTMAk+DrvjdMg+LCTjwkEC0AykENxkdgX7gVNqu8RPdgWri5DYyooZSucIMekAdSiyKmBZsQ2xrYolVpcRXJOe5a3b5hc90RvSraLVChs3igrdHAeAQVXD3vNcf0KqR2n1Q5lNExoDeGTWNpUkwfb0ItTeGXNkQ6RirHarqiMdkrNPVpxJBP7G/InbVg34Vr3/5WYrviPOETKIt3sB4l09EwERYNqsHZSkp1w0+sVg/Dz8S4mfw+o+qFSiFQVeDdV/GqFGupqJxY8TZj/r3h1cO6a9Nl01l9nSPs0+BKgZWt6tHu9pwCK3WgFlgNgUwz/E1ttWlBY86Og6ZiKkCZQiYrbFptZHUeF7eHf2GbF/UvkEun0rMBcLDtmMh1ZUgh2yibqRnLug0MLlsIGFKMWv7OlnNNqR5NoYHIid5aTlHSuLg3NpGSapg27aHkGaFagdWZJ13bAPjw48jhI4BMA9Z/nEuCD2ocIkTdUe4L00n9dxemJGMoDFfxfRjfoYcGKtk2JxmHKfNa/YM7u2UPXzw4mrMR4Nzf5Qn3bNkF/Tf13NtHiPscVcVYYRFX47liDUDHKEaCmr1ncKbf5nNzEYvrqN9zTpcTuzr6GcPng2d2o0cp36sw2L3YSK0m7uZGq1HpQTPa1Kph9o2SCQk+xwMU63t053HL0kQilVJaj5CqwQGplWGuiiZHAvsrbqVeAYApFbcDFJR51jDFXW3vExYAwBdVzR3qDy+F2vuiSqXuERmmCU7BFkiYNTxcNaiXSK7bIGTfGoqpujFcjVkosGgNBPXxah6Ir8kTspuRzyk5u9oq3Ir+Ufi3zrViF1GvsCSQHYpJg5SOq3Kk4r50JlvFmqYAvyK3SMjBhNx7Gn3tsaj302VfIF6aLh3kGfVAkSWDyQGGCDAVHQZiVAX7ZcslNBsSku3R7VtPKUvtvLdsq83F7pp2rqwKAQ3E+LIChdWLPXv466pdqPHXHd3O6lG23jWrDVj8hKoxOeM2UhpveOLuOnXklaROVAKw1VBOVyZxXdtwPYwQcCwbQMc7Puy0vIKbMUDyuiXLnCKKVwNfHlbnVH6of5mWWhR4702p/VwPGQfdBxw5i8KMenGGKUHFi+2KWU9xh+3Laj88vTt8WVSK/fC3YpK5rmzRI4Aki35gLk/lYKSWRUwLQ6dDBdm1wrCmmz1XicVYsC0WM1X277L+BEaG7EMg+kwDYy5YsTzRZfW7o9vbpED24smF/uka5a1ZtiZdrK3RU/lj0DbmZr5GZ97rtmdMYqYfqp46bZs0TCjnIIANW4K5w9IM/dkAixqgaeQfKzVztlocOzSuRWF5RXEtp0Oe8XKZsDMYUbenhzy5eER6FugcB1P3+ihFQalonxaVbtOmem3AcsQakyVFTFHz8I4qk0ZhFB5HrSFPXnLUsZh/lDfedDAUPhQ7iNcf3go7IO8YO1CoSOyAhpGDyg0dy3zPxQ3NOo7va1IsrQhNmSErTyttK/rhmbwkKSk+sNe0lj2LM3nCfhVX1/dSGPT4iQKPDhQvU+KXnTNWzhTmb0ZDBESAPU7yDsF1wT92K98opW8S/Qwo22x7p+7cezRnSBGV2utx+StuRzLPsSmD022ac0DkDuuSMsqbk5uhou/rAiTLNVn+uGNvX2fLLSoa6d222ygsqhwLjPjFcLwKMmP4WOxSNhnRWxm2l3cMmkFSXEsBQX9/KtuydbKLgrL1t7uqkvJw25sDVtuL2ufw0Sz2aE0CLGDKG/gh0CALapDl4PcjS6SGasiS3+xKaSgOohJJBdFkhOrIhGFMTvzQxY3Y+ebbGGtD1Dhu4+fLNZ3Mm0NN24vhFw8za/44FWo85XUzULOLX1u8O8auU1GQVkMN1aCPPMfS/g6WKpgw8j6A9ghjz816h/gBlsMHoC180M0s4/NVDygOyNGYWK28n9JW8+BIEQUAETBwu09oe1cUYPCaAYbPLxUZVjaaSmw1wiSVWNR6LarTkUy3+7C4ffHgvEe5WVaIzi++HMaXSgi7ek9RnS910jOAL+nh4Z/4lJg7/Kck+Ph/ diff --git a/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx b/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx index d0561dfd305a..12e183793bcd 100644 Binary files a/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx and b/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index 3c976908eaa7..16c767e7d088 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/NewLogicComponentClassDiagram.pptx b/docs/diagrams/NewLogicComponentClassDiagram.pptx new file mode 100644 index 000000000000..b6f12daf9a82 Binary files /dev/null and b/docs/diagrams/NewLogicComponentClassDiagram.pptx differ diff --git a/docs/diagrams/ServiceExpensesDiagrams.pptx b/docs/diagrams/ServiceExpensesDiagrams.pptx new file mode 100644 index 000000000000..54ddc6e14eda Binary files /dev/null and b/docs/diagrams/ServiceExpensesDiagrams.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..e3a1b59f68c0 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/images/AddCommand-activity-diagram.png b/docs/images/AddCommand-activity-diagram.png new file mode 100644 index 000000000000..2b5a7c34a422 Binary files /dev/null and b/docs/images/AddCommand-activity-diagram.png differ diff --git a/docs/images/AddCommand-userguide-active.png b/docs/images/AddCommand-userguide-active.png new file mode 100644 index 000000000000..5f507d85afb4 Binary files /dev/null and b/docs/images/AddCommand-userguide-active.png differ diff --git a/docs/images/AddCommand-userguide-notactive.png b/docs/images/AddCommand-userguide-notactive.png new file mode 100644 index 000000000000..10e31bac1cba Binary files /dev/null and b/docs/images/AddCommand-userguide-notactive.png differ diff --git a/docs/images/AddExpenseSequenceDiagram.png b/docs/images/AddExpenseSequenceDiagram.png new file mode 100644 index 000000000000..bce7614e52dc Binary files /dev/null and b/docs/images/AddExpenseSequenceDiagram.png differ diff --git a/docs/images/AutocompleteActivityDiagram.png b/docs/images/AutocompleteActivityDiagram.png new file mode 100644 index 000000000000..f1b006887882 Binary files /dev/null and b/docs/images/AutocompleteActivityDiagram.png differ diff --git a/docs/images/AutocompleteClassDiagram.png b/docs/images/AutocompleteClassDiagram.png new file mode 100644 index 000000000000..4f5c34c97573 Binary files /dev/null and b/docs/images/AutocompleteClassDiagram.png differ diff --git a/docs/images/AutocompleteSequenceDiagram.png b/docs/images/AutocompleteSequenceDiagram.png new file mode 100644 index 000000000000..a79decf33d48 Binary files /dev/null and b/docs/images/AutocompleteSequenceDiagram.png differ diff --git a/docs/images/CheckinCommandSequenceDiagram.png b/docs/images/CheckinCommandSequenceDiagram.png new file mode 100644 index 000000000000..4c1cb6f8c0d5 Binary files /dev/null and b/docs/images/CheckinCommandSequenceDiagram.png differ diff --git a/docs/images/ClearCommand-success.png b/docs/images/ClearCommand-success.png new file mode 100644 index 000000000000..5b52e477a8eb Binary files /dev/null and b/docs/images/ClearCommand-success.png differ diff --git a/docs/images/CommandArchive_class.png b/docs/images/CommandArchive_class.png new file mode 100644 index 000000000000..e9536cc579ed Binary files /dev/null and b/docs/images/CommandArchive_class.png differ diff --git a/docs/images/CommandHistory_class.png b/docs/images/CommandHistory_class.png new file mode 100644 index 000000000000..a777beaf6b77 Binary files /dev/null and b/docs/images/CommandHistory_class.png differ diff --git a/docs/images/ConciergeFinal.png b/docs/images/ConciergeFinal.png new file mode 100644 index 000000000000..e5f48282f719 Binary files /dev/null and b/docs/images/ConciergeFinal.png differ diff --git a/docs/images/ConciergeIconFinal.png b/docs/images/ConciergeIconFinal.png new file mode 100644 index 000000000000..7b847c09ef02 Binary files /dev/null and b/docs/images/ConciergeIconFinal.png differ diff --git a/docs/images/FindCommandUml.png b/docs/images/FindCommandUml.png new file mode 100644 index 000000000000..fbae7af3a357 Binary files /dev/null and b/docs/images/FindCommandUml.png differ diff --git a/docs/images/ListCommandUml.png b/docs/images/ListCommandUml.png new file mode 100644 index 000000000000..684e71295716 Binary files /dev/null and b/docs/images/ListCommandUml.png differ diff --git a/docs/images/LogInCommand-LogInManager-classdiagram.png b/docs/images/LogInCommand-LogInManager-classdiagram.png new file mode 100644 index 000000000000..a3970c30bebd Binary files /dev/null and b/docs/images/LogInCommand-LogInManager-classdiagram.png differ diff --git a/docs/images/LogInCommand-addpasswordentry.PNG b/docs/images/LogInCommand-addpasswordentry.PNG new file mode 100644 index 000000000000..c75c30e38f13 Binary files /dev/null and b/docs/images/LogInCommand-addpasswordentry.PNG differ diff --git a/docs/images/LogInCommand-addpasswordentry.png b/docs/images/LogInCommand-addpasswordentry.png new file mode 100644 index 000000000000..c75c30e38f13 Binary files /dev/null and b/docs/images/LogInCommand-addpasswordentry.png differ diff --git a/docs/images/LogInCommand-clearsuccess.png b/docs/images/LogInCommand-clearsuccess.png new file mode 100644 index 000000000000..e62aa3917300 Binary files /dev/null and b/docs/images/LogInCommand-clearsuccess.png differ diff --git a/docs/images/LogInCommand-generatehash.PNG b/docs/images/LogInCommand-generatehash.PNG new file mode 100644 index 000000000000..7b80788a4d1b Binary files /dev/null and b/docs/images/LogInCommand-generatehash.PNG differ diff --git a/docs/images/LogInCommand-generatehash.png b/docs/images/LogInCommand-generatehash.png new file mode 100644 index 000000000000..7b80788a4d1b Binary files /dev/null and b/docs/images/LogInCommand-generatehash.png differ diff --git a/docs/images/LogInCommand-loginsuccess.png b/docs/images/LogInCommand-loginsuccess.png new file mode 100644 index 000000000000..49b78588318d Binary files /dev/null and b/docs/images/LogInCommand-loginsuccess.png differ diff --git a/docs/images/LogInCommand-newloginsuccess.PNG b/docs/images/LogInCommand-newloginsuccess.PNG new file mode 100644 index 000000000000..f4c3951fc4e2 Binary files /dev/null and b/docs/images/LogInCommand-newloginsuccess.PNG differ diff --git a/docs/images/LogInCommand-newloginsuccess.png b/docs/images/LogInCommand-newloginsuccess.png new file mode 100644 index 000000000000..f4c3951fc4e2 Binary files /dev/null and b/docs/images/LogInCommand-newloginsuccess.png differ diff --git a/docs/images/LogInCommand-notsignedin.png b/docs/images/LogInCommand-notsignedin.png new file mode 100644 index 000000000000..242c39b74ff0 Binary files /dev/null and b/docs/images/LogInCommand-notsignedin.png differ diff --git a/docs/images/LogInCommand-passwordsfile.PNG b/docs/images/LogInCommand-passwordsfile.PNG new file mode 100644 index 000000000000..3789a4dd42ec Binary files /dev/null and b/docs/images/LogInCommand-passwordsfile.PNG differ diff --git a/docs/images/LogInCommand-passwordsfile.png b/docs/images/LogInCommand-passwordsfile.png new file mode 100644 index 000000000000..3789a4dd42ec Binary files /dev/null and b/docs/images/LogInCommand-passwordsfile.png differ diff --git a/docs/images/LogInCommand-sequencediagram.png b/docs/images/LogInCommand-sequencediagram.png new file mode 100644 index 000000000000..980b0eb198b5 Binary files /dev/null and b/docs/images/LogInCommand-sequencediagram.png differ diff --git a/docs/images/LogOutCommand-notsignedin.png b/docs/images/LogOutCommand-notsignedin.png new file mode 100644 index 000000000000..ea655cb920c9 Binary files /dev/null and b/docs/images/LogOutCommand-notsignedin.png differ diff --git a/docs/images/LogOutCommand-success.png b/docs/images/LogOutCommand-success.png new file mode 100644 index 000000000000..808897b3d16d Binary files /dev/null and b/docs/images/LogOutCommand-success.png differ diff --git a/docs/images/ModelClassBetterOopDiagram.png b/docs/images/ModelClassBetterOopDiagram.png index 9ba8eb5e31d0..ab0ce8c673f3 100644 Binary files a/docs/images/ModelClassBetterOopDiagram.png and b/docs/images/ModelClassBetterOopDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 9fb19078b859..8a50b76e8dca 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/NewLogicComponentClassDiagram.png b/docs/images/NewLogicComponentClassDiagram.png new file mode 100644 index 000000000000..c97924271e06 Binary files /dev/null and b/docs/images/NewLogicComponentClassDiagram.png differ diff --git a/docs/images/ServiceCommand-flowchart.png b/docs/images/ServiceCommand-flowchart.png new file mode 100644 index 000000000000..c301fe85b09f Binary files /dev/null and b/docs/images/ServiceCommand-flowchart.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..0dd12b415eb6 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/UI_landing_page.png b/docs/images/UI_landing_page.png new file mode 100644 index 000000000000..f88b50fbe040 Binary files /dev/null and b/docs/images/UI_landing_page.png differ diff --git a/docs/images/UI_login_password.png b/docs/images/UI_login_password.png new file mode 100644 index 000000000000..638d55640d7d Binary files /dev/null and b/docs/images/UI_login_password.png differ diff --git a/docs/images/UI_login_username.png b/docs/images/UI_login_username.png new file mode 100644 index 000000000000..460ceaa79f05 Binary files /dev/null and b/docs/images/UI_login_username.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..f9017bb55f29 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/Ui_old.png b/docs/images/Ui_old.png new file mode 100644 index 000000000000..5ec9c527b49c Binary files /dev/null and b/docs/images/Ui_old.png differ diff --git a/docs/images/adamwth.png b/docs/images/adamwth.png new file mode 100644 index 000000000000..abe1c287a3b2 Binary files /dev/null and b/docs/images/adamwth.png differ diff --git a/docs/images/add.png b/docs/images/add.png new file mode 100644 index 000000000000..13dc3d22edf9 Binary files /dev/null and b/docs/images/add.png differ diff --git a/docs/images/addCommand-class-diagram.png b/docs/images/addCommand-class-diagram.png new file mode 100644 index 000000000000..04c0afd325e8 Binary files /dev/null and b/docs/images/addCommand-class-diagram.png differ diff --git a/docs/images/addPREFIX_NAME.png b/docs/images/addPREFIX_NAME.png new file mode 100644 index 000000000000..b27bf43d725d Binary files /dev/null and b/docs/images/addPREFIX_NAME.png differ diff --git a/docs/images/afteranthonyspace.png b/docs/images/afteranthonyspace.png new file mode 100644 index 000000000000..01debd32ea1e Binary files /dev/null and b/docs/images/afteranthonyspace.png differ diff --git a/docs/images/anthonyspace.png b/docs/images/anthonyspace.png new file mode 100644 index 000000000000..9d139192e614 Binary files /dev/null and b/docs/images/anthonyspace.png differ diff --git a/docs/images/check-in-activity-diagram.png b/docs/images/check-in-activity-diagram.png new file mode 100644 index 000000000000..2375e5199fa6 Binary files /dev/null and b/docs/images/check-in-activity-diagram.png differ diff --git a/docs/images/expense_uml.png b/docs/images/expense_uml.png new file mode 100644 index 000000000000..a2807806b8a9 Binary files /dev/null and b/docs/images/expense_uml.png differ diff --git a/docs/images/fulladdautocomplete.png b/docs/images/fulladdautocomplete.png new file mode 100644 index 000000000000..5f697db707c0 Binary files /dev/null and b/docs/images/fulladdautocomplete.png differ diff --git a/docs/images/icon-important.png b/docs/images/icon-important.png new file mode 100644 index 000000000000..3b65c5700010 Binary files /dev/null and b/docs/images/icon-important.png differ diff --git a/docs/images/icon-key.png b/docs/images/icon-key.png new file mode 100644 index 000000000000..49e47efcec7c Binary files /dev/null and b/docs/images/icon-key.png differ diff --git a/docs/images/jiaqingtan.png b/docs/images/jiaqingtan.png new file mode 100644 index 000000000000..f9fcc76fdef0 Binary files /dev/null and b/docs/images/jiaqingtan.png differ diff --git a/docs/images/neilish3re.png b/docs/images/neilish3re.png new file mode 100644 index 000000000000..d9f71c93dc80 Binary files /dev/null and b/docs/images/neilish3re.png differ diff --git a/docs/images/pikulet.png b/docs/images/pikulet.png new file mode 100644 index 000000000000..61d74dbff285 Binary files /dev/null and b/docs/images/pikulet.png differ diff --git a/docs/images/servicepostclear.png b/docs/images/servicepostclear.png new file mode 100644 index 000000000000..066cf899fa9b Binary files /dev/null and b/docs/images/servicepostclear.png differ diff --git a/docs/images/servicepreclear.png b/docs/images/servicepreclear.png new file mode 100644 index 000000000000..f53abb1a50b9 Binary files /dev/null and b/docs/images/servicepreclear.png differ diff --git a/docs/images/teowz46.png b/docs/images/teowz46.png new file mode 100644 index 000000000000..b11f039dce24 Binary files /dev/null and b/docs/images/teowz46.png differ diff --git a/docs/stylesheets/asciidoctor.css b/docs/stylesheets/asciidoctor.css index 36590bf346cd..5bd5f774cc22 100644 --- a/docs/stylesheets/asciidoctor.css +++ b/docs/stylesheets/asciidoctor.css @@ -378,7 +378,6 @@ p{margin-bottom:1.25rem} *{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important} a{color:inherit!important;text-decoration:underline!important} a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} -a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} abbr[title]:after{content:" (" attr(title) ")"} pre,blockquote,tr,img,object,svg{page-break-inside:avoid} thead{display:table-header-group} diff --git a/docs/team/adamwth.adoc b/docs/team/adamwth.adoc new file mode 100644 index 000000000000..ff9aa1003808 --- /dev/null +++ b/docs/team/adamwth.adoc @@ -0,0 +1,111 @@ += Chew Yong Soon - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: image:ConciergeFinal.png[width="200"] + +--- + +== Overview + +Concierge(TM) is a desktop application simulating a hotel management system used by hotel owners and receptionists. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +* *Code contributed*: [https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=adamwth[Code Contributed]] + +* *Major enhancement*: + +** Implemented `checkIn` and `checkout` commands +*** What it does: Allows the user to check-in a room's booking that is active, checkout any booking of a room, +and update the checked-in guest list and archived guest list to reflect the checked-in/out guests respectively. +*** Justification: This feature is a core feature that supports the daily operations of a hotel, without which the hotel will not +be able to function. The checking-in/out of a room's booking is a very frequent operation in a typical hotel setting. +However, it is very cumbersome to execute these operations using the existing `edit` command as it requires many fields to fill in. +This feature solves this problem by providing a command-line interface with minimal fields to specify, allowing smooth and fast +checking-in/out of rooms. + +*** Highlights: +**** The validity of a check-in operation requires knowledge of how a hotel manages its policy of allowing guests to check-in. +The current implementation was finalized after experimenting with different validity checks on multiple different scenarios. +Refer to the <> and <> +for more information on these checks. +**** As for the checkout feature, though it was initially intended to execute similar validity checks for checking-out a booking, +it has now expanded its scope to double as a delete-booking feature. +**** Before these commands could be implemented, the underlying `Room` architecture had to be written first. Extensive +trial-and-error was conducted to decide the better alternative between mutable and immutable data, from which +the immutability paradigm emerged the victor, as it was easy to debug and made writing unit, integration, and system tests much easier. +As for the data structure of Room, Guests, and Bookings, cyclic references were shunned in favor of one-way references +(i.e. Room contains Bookings; each Booking contains a Guest). This was chosen to minimize the risk of data corruption, as it +removed the need for maintaining the synchronization of the same data in multiple places. + + +* *Minor enhancements*: +*** Add `list -cg` list checked-in guests feature: https://github.com/CS2103-AY1819S1-F11-2/main/pull/178[#178] +*** Add `deselect` feature: https://github.com/CS2103-AY1819S1-F11-2/main/pull/219[#219] +*** Add `reassign` command: https://github.com/CS2103-AY1819S1-F11-2/main/pull/222[#222] + +* *Other contributions*: + +** Project Management +*** Managed releases `v1.2` - `v1.4` (5 releases) on GitHub + +** Writing and testing the `Room` package +*** Single-handedly wrote and tested the entire `Room` package: https://github.com/CS2103-AY1819S1-F11-2/main/pull/71[#71] + +** Enhancements to existing features: +*** Improve GUI display of rooms in both left and right panels: +https://github.com/CS2103-AY1819S1-F11-2/main/pull/115[#115], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/154[#154] +*** Fix `select` command bug and make improvements: https://github.com/CS2103-AY1819S1-F11-2/main/pull/211[#211] + +** Documentation +*** User Guide +**** Update with description of usage of CheckIn, Checkout, and Reassign commands +**** Did cosmetic tweaks (icons and format) to existing contents of the User Guide: https://github.com/CS2103-AY1819S1-F11-2/main/pull/238[#238] + +*** Developer Guide +**** Update with detailed description of how CheckIn and Checkout features work +**** Add Sequence and Activity diagrams for CheckIn and Checkout features as aids to explain control flow: +https://github.com/CS2103-AY1819S1-F11-2/main/pull/150[#150], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/238[#238] + +** Tools +*** Added Coveralls, Appveyor, and Codacy support for the team + +** Community +*** Opened 40+ https://github.com/CS2103-AY1819S1-F11-2/main/pulls?q=is%3Apr+assignee%3Aadamwth+is%3Aclosed[pull requests] +*** Reviewed 40+ https://github.com/CS2103-AY1819S1-F11-2/main/pulls?q=is%3Apr+reviewed-by%3Aadamwth+is%3Aclosed[pull requests] +*** Some examples of PRs reviewed (with non-trivial review comments), giving praise and encouragement where it is due: +https://github.com/CS2103-AY1819S1-F11-2/main/pull/63[#63], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/89[#89], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/105[#105], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/155[#155], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/158[#158], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/169[#169], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/226[#226] + + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=terminology] + +include::../UserGuide.adoc[tag=checkin] + +include::../UserGuide.adoc[tag=checkout] + +include::../UserGuide.adoc[tag=reassign] + +== 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=checkin] + diff --git a/docs/team/jiaqingtan.adoc b/docs/team/jiaqingtan.adoc new file mode 100644 index 000000000000..2e9f5b814c57 --- /dev/null +++ b/docs/team/jiaqingtan.adoc @@ -0,0 +1,59 @@ += Tan Jiaqing - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Concierge + +--- + +== Overview + +Concierge is a desktop application simulating a hotel management system used by hotel owners and receptionists. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +* *Code contributed*: [https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=jiaqingtan[Code Contributed]] + +* *Major enhancements*: + +** *Expanding on the existing `list` function:* +*** *What it does:* It allows the user to list between seeing a list of all guests, list of checked-in guests, or a list of all the rooms in the hotel. +*** *Justification:* This feature is essential to a proper, working hotel management system. The listing of information on guests and rooms is needed for receptionists to perform follow-up tasks, be it in person or within the app. +*** *Highlights:* The listing function was made with the intention of listing guests and rooms separately under the same column by modifying JAVAFX and FXML files. +*** *Credits:* Additional credit should also be given to my groupmate *Adam Chew (adamwth)*, who added the functionality of list checked-in guests with the `list -cg` command. + +** *Expanding on the existing `find` function:* +*** *What it does:* It allows the user to search for all guests, checked-in guests, or rooms based on a particular set of conditions/predicates related to the attributes of the guest/room. +*** *Justification:* This feature is essential to a proper, working hotel management system. The find function can be used in a multitude of scenarios, such as when a quick search of rooms of an exact capacity is needed, or when a checked-in guest needs to be located immediately, in the event of an emergency. +*** *Highlights:* The prefixes of the find function can be chained in any order, and in any quantity, as the predicates are all combined at the end. Each filter is has an "and" relationship with one another. +*** *Credits:* The modification of the find function was dependent on its own initial code, and the style of coding and arriving at the solution was closely followed. + +* *Other contributions*: + +** *Replacing the HTML panel with a focused/detailed panel* - https://github.com/CS2103-AY1819S1-F11-2/main/pull/145[#145] + +** *Modifying the FXML files to list guest and rooms under the same UI column/panel* - https://github.com/CS2103-AY1819S1-F11-2/main/pull/145[#145] + +** *Creation of the Concierge Icon and Logo* - https://github.com/CS2103-AY1819S1-F11-2/main/pull/46[#46] + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=find] + +include::../UserGuide.adoc[tag=list] + +== 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=find] + +include::../DeveloperGuide.adoc[tag=list] + diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 453c2152ab9d..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,72 +0,0 @@ -= John Doe - Project Portfolio -:site-section: AboutUs -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -== Summary of contributions - -* *Major enhancement*: added *the ability to undo/redo previous commands* -** What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. -** Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. -** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. -** Credits: _{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== 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=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/neilish3re.adoc b/docs/team/neilish3re.adoc new file mode 100644 index 000000000000..bf854b0fdff8 --- /dev/null +++ b/docs/team/neilish3re.adoc @@ -0,0 +1,98 @@ += Neil Mehta - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: image:ConciergeFinal.png[width="200"] + +--- + +== Overview + +Concierge(TM) is a desktop hotel management application for receptionists to +handle potential bookings and current guests. The user interacts with it +using a CLI, and it has a GUI created with JavaFX. It is written in Java, and +has about 10 kLoC. + +== Summary of contributions + +* *Code contributed*: [https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=neilish3re[Code Contributed]] + +* *Major enhancements*: +** *Implemented Autocomplete Feature* + +*** What it does: Ihe Autocomplete feature allows the user to seamlessly type +in user commands with the autocompletion of an incomplete command that is +input by the user. This feature helps the user by prompting the correct +format. + +*** Justification: This feature is not mandatory to have, however it would +save the user a lot of time, especially if he/she is dealing with many +different commands (in our case, many guests waiting to check in at the same +time). + +*** Highlights: It also eliminates the need for the user to remember all +the prefixes because my implementation not only fills in the command, but fills in +subsequent prefixes for the user as well which makes the feature more effective +and efficient. + +*** Credits: [https://github.com/CS2103-AY1819S1-T09-4/main/blob/master/docs/DeveloperGuide.adoc#autocomplete-feature[Idea]] +Got the idea of implementing the autocomplete feature replete with prefixes +and the various parameters pre-filled in from the link given. However, I decided +to implement it with a Control button press so as to minimise the user having +to navigate back and forth in the Command Box and it will also make for quicker +typing once the user gets a flow of the autocomplete feature. + +* *Minor enhancements*: +** *Implemented Command Archive feature* (Shifted to v2.0) + +*** What it does: This feature keeps an archive of all the commands (both +valid and invalid) input by a user and adds a time stamp plus user tag. This is +different from the history command because this information will be exported +into a txt/xml file instead of being destroyed at the end of the session as +is seen in AB4 + +*** Justification: This feature is not mandatory, however it would serve an +important purpose to hotel managers or the person-in-charge. This feature is +aimed at tracking and identifying any suspicious activity and also to serve +as data that could either be used in the future for monthly audits and even +possibly generating statistics as to what items/facilities are patronised +most by the guests. + +* *Other contributions*: + +** Setting up the initial user guide with all the new commands we aimed to +implement +** PRs reviewed (with some review comments): +#133, #139, #140, #143, #153, #162, #191, #229, #235, #236 +** Reported bugs and suggestions for other teams in the class: +https://github.com/CS2103-AY1819S1-W16-3/main/issues/170[unclear usage of find feature], +https://github.com/CS2103-AY1819S1-W16-3/main/issues/167[unable to edit multiple parameters at once], +https://github.com/CS2103-AY1819S1-W16-3/main/issues/164[unclear purpose of back command] +https://github.com/CS2103-AY1819S1-W16-3/main/issues/160[/bk prompt of add command] + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my +ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=autocomplete] + +include::../UserGuide.adoc[tag=archive] + +== 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=autocomplete] + +include::../DeveloperGuide.adoc[tag=archive] + + + diff --git a/docs/team/pikulet.adoc b/docs/team/pikulet.adoc new file mode 100644 index 000000000000..c1401216f7e5 --- /dev/null +++ b/docs/team/pikulet.adoc @@ -0,0 +1,112 @@ += Joyce Yeo Shuhui - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: image:ConciergeFinal.png[width="200"] + +--- + +== Overview + +https://github.com/CS2103-AY1819S1-F11-2/main[Concierge(TM)] is a desktop +hotel management application for receptionists to handle potential bookings +and current guests. The user interacts with it using a CLI, and it has a GUI +created with JavaFX. It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +[TIP] +Access my contributed code +https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=pikulet[here]. + +*Major enhancement*: added *the ability to login/logout* of the system. + + +* What it does: Allows users to login to Concierge and access restricted +commands which mutate the data. +* Significance: With this feature, hotel managers can implement some level +of access control. Some features more commonly used (`find`, `list`) can +still be accessed without signing in. By combining this with the ability + to export the command history, the auditing process for rogue commands is + expedited. +* Highlights: +** The login/logout feature is dynamic - the users are not prompted to sign in +upon starting Concierge, and users can logout and log back in within the same + session. This means that there is a very close integration with the existing + commands to verify the sign-in status and whether the commands require + signing-in. +** This feature is complete - I worked with the different architectural +components of Concierge, from command parsing (`Logic`) to login verification + (`Model`) and even password storage (`Storage`). +** The feature attempts to achieve some level of security with SHA-256 +hashing. +* Credits: The password hash algorithm was taken from https://www.baeldung.com/sha-256-hashing-java[Baeldung]. +* These features were mainly achieved in +https://github.com/CS2103-AY1819S1-F11-2/main/pull/168[#168] and +https://github.com/CS2103-AY1819S1-F11-2/main/pull/226[#226]. + +*Minor enhancements/ code contributed*: + +* https://github.com/CS2103-AY1819S1-F11-2/main/pull/133[Renamed] + the existing classes and methods from `Person` to `Guest` +* Removed https://github.com/CS2103-AY1819S1-F11-2/main/pull/143[`Address`] + as a field in `Person`, removed the +https://github.com/CS2103-AY1819S1-F11-2/main/pull/233[edit] and +https://github.com/CS2103-AY1819S1-F11-2/main/pull/139[delete] commands +* Modified the https://github.com/CS2103-AY1819S1-F11-2/main/pull/104[`add`] +command to take in room and date details. Room and Booking package by others. +* Modified the https://github.com/CS2103-AY1819S1-F11-2/main/pull/162[`clear`] + command to maintain empty hotel rooms +* Added function for +https://github.com/CS2103-AY1819S1-F11-2/main/pull/162[GUI verification of rooms] +* Added most of the +https://github.com/CS2103-AY1819S1-F11-2/main/pull/42[Appendices] in the +Developer Guide + +*Project Management*: + +* Managed the team's issue tracker +* Encouraged team to use clean PRs, TODO and Codacy +* Set up https://github.com/CS2103-AY1819S1-F11-2/main/pull/159[RepoSense] for + the team +* Community +** PRs reviewed (with non-trivial review comments): +https://github.com/CS2103-AY1819S1-F11-2/main/pull/157[#157], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/71[#71], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/156[#156], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/111[#111], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/100[#100], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/222[#222], +https://github.com/CS2103-AY1819S1-F11-2/main/pull/219[#219] +** Reported bugs and suggestions for other teams in the class +*** https://github.com/CS2103-AY1819S1-W14-3/main/issues/161[Discovered impersistence in data], +https://github.com/CS2103-AY1819S1-W14-3/main/issues/148[discovered hidden bug by rearranging command], +https://github.com/CS2103-AY1819S1-W14-3/main/issues/157[made suggestion on bounds checking] + +== Contributions to the User Guide + +|=== +|_My contributions to the User Guide below showcase my ability to write +documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=add] + +include::../UserGuide.adoc[tag=loginlogout] + +== Contributions to the Developer Guide + +|=== +|_My contributions to the Developer Guide below showcase my ability to write +technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=add] + +include::../DeveloperGuide.adoc[tag=loginlogout] + +== End Note + +I am grateful to have had the opportunity to work on this project with +amazing team members who each contributed with their own strengths. Thank +you, @adamwth @teowz46 @JiaqingTan @neilish3re. diff --git a/docs/team/teowz46.adoc b/docs/team/teowz46.adoc new file mode 100644 index 000000000000..ac7e95939aaf --- /dev/null +++ b/docs/team/teowz46.adoc @@ -0,0 +1,97 @@ += Teo Wei Zheng - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Concierge(TM) + +--- + +== Overview + +Concierge(TM) is a desktop application simulating a hotel management system used by hotel +owners and receptionists. The user interacts with it using a CLI, and it has a GUI created +with JavaFX. It is written in Java, and has about 10 kLoC. + +== Summary of contributions + +* *Code contributed*: [https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=teowz46[Code Contributed]] + +* *Major enhancement*: Implemented the entire `service` architecture and command + +** What it does: The `service` command allows for the integration of all the hotel's goods + and services into Concierge(TM). Users will be able to use Concierge(TM) to charge and + keep track of the expenditure of all guests on the hotel's goods and services. + +** Justification: A hotel often provides many different kinds of goods and services, such as + room service, restaurants, minibar, swimming pool, etc. Since all of these goods and + services belong to the hotel, there should be a centralised system to keep track of all + the expenditure on the hotel's goods and services, such that the expenses can be compiled + and presented to the guest conveniently during checkout. + +** Highlights: This functionality required the implementation of several different classes + to serve as the architecture. Firstly, `Expenses` and `Expense` were required to hold the + information regarding the `Room`'s total expenditure and each individual expenditure + respectively. Secondly, a `Menu` of `ExpenseType` objects were required to serve as the + main reference of all the items that are sold in the hotel. This serves as the catalogue + for the hotel. Finally, a `Money` class was created to handle monetary values. In addition, + the information in all of these classes need to be stored in XML, and therefore implementing + this required significant effort to convert each class into an `XmlAdaptedClass` and to + handle all the possible errors that can be introduced by modifying the values in the XML. + Testing also required significant effort in adding several new test files and implementing + several new utility methods, besides writing tests for all the classes mentioned above. + +* *Minor enhancement*: added a GUI panel that displays the expenditure details of every room. + +* *Other contributions*: + +** Refactoring: +*** Replaced all instances and variants of "Address Book" with "Concierge". + +** Documentation: +*** Modified the existing Model UML diagram with our architecture: https://github.com/CS2103-AY1819S1-F11-2/main/pull/247[#247] +*** Updated user stories in developer guide, made basic changes to the website: https://github.com/CS2103-AY1819S1-F11-2/main/pull/45[#45] + +** Community: +*** PRs reviewed (with non-trivial review comments): + https://github.com/CS2103-AY1819S1-F11-2/main/pull/90[#90], + https://github.com/CS2103-AY1819S1-F11-2/main/pull/169[#169] +*** Reported bugs and suggestions for other teams in the class (examples: + https://github.com/CS2103-AY1819S1-T13-1/main/issues/130[1], + https://github.com/CS2103-AY1819S1-T13-1/main/issues/137[2], + https://github.com/CS2103-AY1819S1-T13-1/main/issues/139[3], + https://github.com/CS2103-AY1819S1-T13-1/main/issues/147[4], + https://github.com/CS2103-AY1819S1-T13-1/main/issues/153[5], + https://github.com/CS2103-AY1819S1-T13-1/main/issues/157[6], + https://github.com/CS2103-AY1819S1-T13-1/main/issues/158[7], ) + +** Tools: +*** Integrated Travis CI to the project. + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== +* Links to relevant sections in User Guide (for viewing on Github) +** https://github.com/CS2103-AY1819S1-F11-2/main/blob/master/docs/UserGuide.adoc#provide-room-service-code-service-code-span-class-image-img-src-images-icon-key-png-alt-icon-key-width-32-span[`service` command] + +include::../UserGuide.adoc[tag=service] + +== 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._ +|=== +* Links to relevant sections in Developer Guide (for viewing on Github) +** https://github.com/CS2103-AY1819S1-F11-2/main/blob/master/docs/DeveloperGuide.adoc#expense-expenses-and-expensetype[Expense, Expenses, and ExpenseType] +** https://github.com/CS2103-AY1819S1-F11-2/main/blob/master/docs/DeveloperGuide.adoc#money[Money] +** https://github.com/CS2103-AY1819S1-F11-2/main/blob/master/docs/DeveloperGuide.adoc#servicecommand[ServiceCommand] + + +include::../DeveloperGuide.adoc[tag=expenses] + +include::../DeveloperGuide.adoc[tag=money] + +include::../DeveloperGuide.adoc[tag=service] + diff --git a/docs/templates/LICENSE b/docs/templates/LICENSE index 2073b44dee64..c76d5c0e8d54 100644 --- a/docs/templates/LICENSE +++ b/docs/templates/LICENSE @@ -9,7 +9,7 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is +copies of the Software, and to permit guests to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index ecdd043a4f81..a7e6c6c0d2ef 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -1,5 +1,8 @@ package seedu.address; +import static seedu.address.model.util.SampleDataUtil.getDefaultPasswordHashList; +import static seedu.address.model.util.SampleDataUtil.getSampleConcierge; + import java.io.IOException; import java.nio.file.Path; import java.util.Optional; @@ -20,18 +23,20 @@ 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.ReadOnlyConcierge; import seedu.address.model.UserPrefs; +import seedu.address.model.login.PasswordHashList; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; +import seedu.address.storage.ConciergeStorage; +import seedu.address.storage.JsonPasswordsStorage; import seedu.address.storage.JsonUserPrefsStorage; +import seedu.address.storage.PasswordsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; -import seedu.address.storage.XmlAddressBookStorage; +import seedu.address.storage.XmlConciergeStorage; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; @@ -40,7 +45,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 4, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -54,7 +59,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing Concierge ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -62,8 +67,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); + ConciergeStorage conciergeStorage = new XmlConciergeStorage(userPrefs.getConciergeFilePath()); + PasswordsStorage passwordsStorage = new JsonPasswordsStorage(userPrefs.getPasswordsFilePath()); + storage = new StorageManager(conciergeStorage, userPrefsStorage, passwordsStorage); initLogging(config); @@ -77,28 +83,33 @@ 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 Concierge and {@code userPrefs}.
+ * The data from the sample Concierge will be used instead if {@code storage}'s Concierge is not found, + * or an empty Concierge will be used instead if errors occur when reading {@code storage}'s Concierge. */ private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional conciergeOptional; + ReadOnlyConcierge initialData; + PasswordHashList passwordRef = initPasswordStorage(storage); + try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + conciergeOptional = storage.readConcierge(); + if (!conciergeOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample Concierge"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = + conciergeOptional.orElseGet(SampleDataUtil::getSampleConcierge); } 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 a sample Concierge"); + initialData = getSampleConcierge(); } 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 a sample Concierge"); + initialData = getSampleConcierge(); } - return new ModelManager(initialData, userPrefs); + return new ModelManager(initialData, userPrefs, passwordRef); } private void initLogging(Config config) { @@ -159,7 +170,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 Concierge"); initializedPrefs = new UserPrefs(); } @@ -173,19 +184,56 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { return initializedPrefs; } + /** + * Returns a {@code PasswordHashList} using the storage data. + * If there is an invalid or missing passwords.json file, then an empty + * one with the default data will be created. + */ + protected PasswordHashList initPasswordStorage(Storage storage) { + PasswordHashList passwords; + + try { + Optional passwordsOptional = storage.readPasswordRef(); + if (!passwordsOptional.isPresent()) { + logger.info("Password file not found. " + + "Will be starting with a sample login account."); + } + + passwords = + passwordsOptional.orElseGet(SampleDataUtil::getDefaultPasswordHashList); + } catch (DataConversionException e) { + logger.warning("Data file not in the correct format. " + + "Will be starting with a sample login account."); + passwords = getDefaultPasswordHashList(); + } catch (IOException e) { + logger.warning("Problem while reading from the file. " + + "Will be starting with a sample login account."); + passwords = getDefaultPasswordHashList(); + } + + // Update prefs file in case it was missing to begin with or there are new/unused fields + try { + storage.savePasswordRef(passwords); + } catch (IOException e) { + logger.warning("Failed to save password file : " + StringUtil.getDetails(e)); + } + + return passwords; + } + private void initEventsCenter() { EventsCenter.getInstance().registerHandler(this); } @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting Concierge " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping Concierge ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index e978d621e086..693f4cc122ba 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 = "Concierge"; 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..cc8d28e494bd 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -20,7 +20,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "concierge.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..d69dbd63ccee 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -1,13 +1,27 @@ package seedu.address.commons.core; +import seedu.address.model.room.RoomNumber; + /** * Container for user visible messages. */ 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_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_GUEST_DISPLAYED_INDEX = "The guest index provided is invalid"; + public static final String MESSAGE_INVALID_CHECKED_IN_GUEST_DISPLAYED_INDEX = + "The checked-in guest index provided is invalid"; + public static final String MESSAGE_INVALID_ROOM_DISPLAYED_INDEX = "The room index provided is invalid"; + public static final String MESSAGE_INVALID_DATE = "The date provided is invalid"; + public static final String MESSAGE_NO_LIST_DISPLAYED = "No list is being displayed"; + public static final String MESSAGE_GUESTS_LISTED_OVERVIEW = "%1$d guests listed"; + public static final String MESSAGE_ROOMS_LISTED_OVERVIEW = "%1$d rooms listed"; + + public static final String MESSAGE_VALID_ROOM = + "The room number provided must be a 3-digit positive integer from 001 to " + + RoomNumber.MAX_ROOM_NUMBER + "\n"; + public static final String MESSAGE_VALID_DATE = + "The date provided must be in d/M/y format and valid according to the Gregorian calendar. " + + "Day and Month can be 1 or 2 digits, Year can be 2 or 4 digits.\n"; } 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..f36de55fd557 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -9,6 +9,10 @@ * convert it back to an int if the index will not be passed to a different component again. */ public class Index { + + public static final String MESSAGE_INDEX_CONSTRAINTS = + "Index is not a non-zero unsigned integer."; + private int zeroBasedIndex; /** 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/ConciergeChangedEvent.java b/src/main/java/seedu/address/commons/events/model/ConciergeChangedEvent.java new file mode 100644 index 000000000000..52fdeaed7a7c --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/ConciergeChangedEvent.java @@ -0,0 +1,19 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.ReadOnlyConcierge; + +/** Indicates the Concierge in the model has changed*/ +public class ConciergeChangedEvent extends BaseEvent { + + public final ReadOnlyConcierge data; + + public ConciergeChangedEvent(ReadOnlyConcierge data) { + this.data = data; + } + + @Override + public String toString() { + return "number of guests " + data.getGuestList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/DeselectGuestListEvent.java b/src/main/java/seedu/address/commons/events/ui/DeselectGuestListEvent.java new file mode 100644 index 000000000000..ea3b469de29d --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/DeselectGuestListEvent.java @@ -0,0 +1,15 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; + +/** + * Represents a deselect guest list event + */ +public class DeselectGuestListEvent extends BaseEvent { + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/address/commons/events/ui/GuestPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/GuestPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..6ecf89f56bde --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/GuestPanelSelectionChangedEvent.java @@ -0,0 +1,25 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.guest.Guest; + +/** + * Represents a selection change in the Guest List Panel + */ +public class GuestPanelSelectionChangedEvent extends BaseEvent { + + private final Guest newSelection; + + public GuestPanelSelectionChangedEvent(Guest newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public Guest getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java index a890f8b47350..ca7dabfc8386 100644 --- a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java +++ b/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java @@ -2,16 +2,19 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.events.BaseEvent; +import seedu.address.logic.parser.Prefix; /** - * Indicates a request to jump to the list of persons + * Indicates a request to jump to the list of guests */ public class JumpToListRequestEvent extends BaseEvent { public final int targetIndex; + public final Prefix flag; - public JumpToListRequestEvent(Index targetIndex) { + public JumpToListRequestEvent(Index targetIndex, Prefix flag) { this.targetIndex = targetIndex.getZeroBased(); + this.flag = flag; } @Override diff --git a/src/main/java/seedu/address/commons/events/ui/ListingChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/ListingChangedEvent.java new file mode 100644 index 000000000000..0bfffc290ad2 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/ListingChangedEvent.java @@ -0,0 +1,25 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.logic.parser.Prefix; + +/** + * Represents a listing change in the left UI column + */ +public class ListingChangedEvent extends BaseEvent { + + private Prefix flag; + + public ListingChangedEvent(Prefix flag) { + this.flag = flag; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public Prefix getFlag() { + return flag; + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java deleted file mode 100644 index c5c8b9ce90ed..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.person.Person; - -/** - * Represents a selection change in the Person List Panel - */ -public class PersonPanelSelectionChangedEvent extends BaseEvent { - - - private final Person newSelection; - - public PersonPanelSelectionChangedEvent(Person newSelection) { - this.newSelection = newSelection; - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - - public Person getNewSelection() { - return newSelection; - } -} diff --git a/src/main/java/seedu/address/commons/events/ui/RoomListChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/RoomListChangedEvent.java new file mode 100644 index 000000000000..2b8d9b2d5854 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/RoomListChangedEvent.java @@ -0,0 +1,26 @@ +package seedu.address.commons.events.ui; + +import javafx.collections.ObservableList; +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.room.Room; + +/** + * Represents a room list change in the left UI column + */ +public class RoomListChangedEvent extends BaseEvent { + + private ObservableList roomList; + + public RoomListChangedEvent(ObservableList roomList) { + this.roomList = roomList; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public ObservableList getRoomList() { + return roomList; + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/RoomPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/RoomPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..ca9422b008e3 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/RoomPanelSelectionChangedEvent.java @@ -0,0 +1,25 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.room.Room; + +/** + * Represents a selection change in the room List Panel + */ +public class RoomPanelSelectionChangedEvent extends BaseEvent { + + private final Room newSelection; + + public RoomPanelSelectionChangedEvent(Room newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public Room getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/address/commons/util/JsonUtil.java index 8ef609f055df..f0fdef3769d9 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/address/commons/util/JsonUtil.java @@ -14,6 +14,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer; @@ -90,7 +91,6 @@ public static void saveJsonFile(T jsonFile, Path filePath) throws IOExceptio serializeObjectToJsonFile(filePath, jsonFile); } - /** * Converts a given string representation of a JSON data to instance of a class * @param The generic type to create an instance of @@ -100,6 +100,16 @@ public static T fromJsonString(String json, Class instanceClass) throws I return objectMapper.readValue(json, instanceClass); } + /** + * Converts a given string representation to a JsonPObject. + * + * @param json The json string to be parsed + * @return The JsonNode object which allows mapping of key to values. + */ + public static JsonNode getNodeObject(String json) throws IOException { + return objectMapper.readTree(json); + } + /** * 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 diff --git a/src/main/java/seedu/address/commons/util/XmlUtil.java b/src/main/java/seedu/address/commons/util/XmlUtil.java index a78cd15b7f0c..8a7aeea3612f 100644 --- a/src/main/java/seedu/address/commons/util/XmlUtil.java +++ b/src/main/java/seedu/address/commons/util/XmlUtil.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import java.io.FileNotFoundException; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -68,4 +69,27 @@ public static void saveDataToFile(Path file, T data) throws FileNotFoundExce m.marshal(data, file.toFile()); } + /** + * Saves the data in the file in xml format. Creates the file if it doesn't yet exist. + * + * @param file Points to a valid xml file containing data that match the {@code classToConvert}. + * Cannot be null. + * @throws IOException Thrown if the filepath cannot be created + * @throws JAXBException Thrown if there is an error during converting the data + * into xml and writing to the file. + */ + public static void saveDataToFileIfMissing(Path file, T data) throws JAXBException, IOException { + + requireNonNull(file); + requireNonNull(data); + + FileUtil.createIfMissing(file); + + JAXBContext context = JAXBContext.newInstance(data.getClass()); + Marshaller m = context.createMarshaller(); + m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + + m.marshal(data, file.toFile()); + } + } diff --git a/src/main/java/seedu/address/logic/CommandArchive.java b/src/main/java/seedu/address/logic/CommandArchive.java new file mode 100644 index 000000000000..2afc2de8b339 --- /dev/null +++ b/src/main/java/seedu/address/logic/CommandArchive.java @@ -0,0 +1,51 @@ +package seedu.address.logic; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.address.commons.util.FileUtil; +import seedu.address.model.expenses.Expense; + +/** + * Append all commands that user entered into command archive + */ +public class CommandArchive { + /** + * Create a logger without hard-coding the class name + */ + private static final Logger LOGGER = Logger.getLogger( + Thread.currentThread().getStackTrace()[0].getClassName()); + + /** + * Takes in the userInputHistory string, extracts the latest command and writes it to commandFile.txt with a + */ + public static void stringToFile (String inputString) { + Path commandHistory = Paths.get("commands_log", "commands_log.txt"); + try { + FileUtil.createIfMissing(commandHistory); + + String latestUserCommand = inputString.substring(0, inputString.indexOf( + System.getProperty("line.separator"))); + String timeStamp = LocalDateTime.now().format(Expense.DATETIME_FORMAT) + " "; + + FileWriter fileWriter = new FileWriter(commandHistory.toFile(), true); + BufferedWriter buffer = new BufferedWriter(fileWriter); + PrintWriter printWriter = new PrintWriter(buffer); + printWriter.print(timeStamp); + printWriter.print(latestUserCommand); + printWriter.print(System.getProperty("line.separator")); + printWriter.close(); + + } catch (IOException e) { + LOGGER.log(Level.INFO, "An IOException was caught", e); + } + } + +} diff --git a/src/main/java/seedu/address/logic/CommandHistory.java b/src/main/java/seedu/address/logic/CommandHistory.java index 39bca9b8df57..aca9b9307b53 100644 --- a/src/main/java/seedu/address/logic/CommandHistory.java +++ b/src/main/java/seedu/address/logic/CommandHistory.java @@ -25,6 +25,25 @@ public CommandHistory(CommandHistory commandHistory) { public void add(String userInput) { requireNonNull(userInput); userInputHistory.add(userInput); + + /** + * Converts userInputHistory from a linked list into a string so that it can be passed to the CommandArchive + * class where the latest command can be extracted and added to commandFile.txt which tracks all the keystrokes + * of a user with a timestamp + */ + /* + String newLine = System.getProperty("line.separator"); + StringBuilder string = new StringBuilder(); + Iterator it = userInputHistory.descendingIterator(); + + while (it.hasNext()) { + string.append(it.next() + newLine); + } + String inputString = string.toString(); + System.out.println(inputString); + // Fix this!!! + // CommandArchive.stringToFile(inputString); + */ } /** diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..de4e687bbb22 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -1,10 +1,13 @@ package seedu.address.logic; +import java.util.List; + import javafx.collections.ObservableList; 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.guest.Guest; +import seedu.address.model.room.Room; /** * API of the Logic component @@ -19,9 +22,27 @@ public interface Logic { */ 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 guests */ + ObservableList getFilteredGuestList(); + + /** Returns an unmodifiable view of the filtered list of checked-in guests */ + public ObservableList getFilteredCheckedInGuestList(); + + /** Returns an unmodifiable view of the filtered list of rooms */ + ObservableList getFilteredRoomList(); /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); + + /** + * Returns the sorted list of autocomplete commands with given prefix string, encapsulated in a String list + * object + */ + List getAutoCompleteCommands(String prefix); + + /** + * Returns the next missing parameter with given input text, or an empty string if there is no next prefix + */ + String getAutoCompleteNextParameter(String inputText); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9aff86fc33dc..ec27ac253535 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -1,17 +1,21 @@ package seedu.address.logic; +import java.util.List; import java.util.logging.Logger; import javafx.collections.ObservableList; import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.autocomplete.AutoCompleteManager; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.commands.exceptions.UnauthorisedCommandException; +import seedu.address.logic.parser.ConciergeParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.guest.Guest; +import seedu.address.model.room.Room; /** * The main LogicManager of the app. @@ -21,19 +25,25 @@ public class LogicManager extends ComponentManager implements Logic { private final Model model; private final CommandHistory history; - private final AddressBookParser addressBookParser; + private final ConciergeParser conciergeParser; + + private final AutoCompleteManager autoCompleteManager; public LogicManager(Model model) { this.model = model; history = new CommandHistory(); - addressBookParser = new AddressBookParser(); + conciergeParser = new ConciergeParser(); + autoCompleteManager = new AutoCompleteManager(); } @Override public CommandResult execute(String commandText) throws CommandException, ParseException { logger.info("----------------[USER COMMAND][" + commandText + "]"); try { - Command command = addressBookParser.parseCommand(commandText); + Command command = conciergeParser.parseCommand(commandText); + if (command.requiresSignIn() && !model.isSignedIn()) { + throw new UnauthorisedCommandException(); + } return command.execute(model, history); } finally { history.add(commandText); @@ -41,12 +51,31 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredGuestList() { + return model.getFilteredGuestList(); + } + + @Override + public ObservableList getFilteredCheckedInGuestList() { + return model.getFilteredCheckedInGuestList(); + } + + @Override + public ObservableList getFilteredRoomList() { + return model.getFilteredRoomList(); } @Override public ListElementPointer getHistorySnapshot() { return new ListElementPointer(history.getHistory()); } + + @Override + public List getAutoCompleteCommands(String commandPrefix) { + return autoCompleteManager.getAutoCompleteCommands(commandPrefix); + } + @Override + public String getAutoCompleteNextParameter(String inputText) { + return autoCompleteManager.getAutoCompleteNextMissingParameter(inputText); + } } diff --git a/src/main/java/seedu/address/logic/autocomplete/AutoCompleteManager.java b/src/main/java/seedu/address/logic/autocomplete/AutoCompleteManager.java new file mode 100644 index 000000000000..21ef02d40ad8 --- /dev/null +++ b/src/main/java/seedu/address/logic/autocomplete/AutoCompleteManager.java @@ -0,0 +1,102 @@ +package seedu.address.logic.autocomplete; + +import static java.util.Objects.requireNonNull; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROOM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.CheckInCommand; +import seedu.address.logic.commands.CheckoutCommand; +import seedu.address.logic.commands.ClearCommand; +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.LogInCommand; +import seedu.address.logic.commands.LogOutCommand; +import seedu.address.logic.commands.ReassignCommand; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.ServiceCommand; +import seedu.address.logic.commands.UndoCommand; +import seedu.address.logic.parser.Prefix; + +/** + * AutoCompleteManager of the application + */ +public class AutoCompleteManager { + + private static final String EMPTY_STRING = ""; + + private Trie commandTrie; + private CommandParameterSyntaxHandler commandParameterSyntaxHandler; + + public AutoCompleteManager() { + commandTrie = new Trie(); + commandParameterSyntaxHandler = new CommandParameterSyntaxHandler(); + initCommandKeyWords(); + } + + /** + * Initialises command keywords in commandTrie + */ + private void initCommandKeyWords() { + commandTrie.insertWord(ClearCommand.COMMAND_WORD); + commandTrie.insertWord(ExitCommand.COMMAND_WORD); + commandTrie.insertWord(FindCommand.COMMAND_WORD); + commandTrie.insertWord(HelpCommand.COMMAND_WORD); + commandTrie.insertWord(HistoryCommand.COMMAND_WORD); + commandTrie.insertWord(ListCommand.COMMAND_WORD); + commandTrie.insertWord(LogOutCommand.COMMAND_WORD); + commandTrie.insertWord(RedoCommand.COMMAND_WORD); + commandTrie.insertWord(SelectCommand.COMMAND_WORD); + commandTrie.insertWord(UndoCommand.COMMAND_WORD); + + commandTrie.insertWord(AddCommand.COMMAND_WORD + " " + PREFIX_NAME); + commandTrie.insertWord(CheckInCommand.COMMAND_WORD + " " + PREFIX_ROOM); + commandTrie.insertWord(CheckoutCommand.COMMAND_WORD + " " + PREFIX_ROOM); + commandTrie.insertWord(LogInCommand.COMMAND_WORD + " " + PREFIX_USERNAME); + commandTrie.insertWord(ReassignCommand.COMMAND_WORD + " " + PREFIX_ROOM); + commandTrie.insertWord(ServiceCommand.COMMAND_WORD + " " + PREFIX_ROOM); + + } + + /** + * Returns sorted list of auto-completed commands with prefix + */ + public List getAutoCompleteCommands(String commandPrefix) { + requireNonNull(commandPrefix); + return commandTrie.autoComplete(commandPrefix).stream() + .sorted(Comparator.comparingInt(String::length)) + .collect(Collectors.toList()); + } + + /** + * Returns the next missing parameter of the inputText or + * an empty string if there is no prefix + */ + public String getAutoCompleteNextMissingParameter(String inputText) { + requireNonNull(inputText); + if (inputText.isEmpty()) { + return EMPTY_STRING; + } + String command = inputText.split(" ")[0]; + + ArrayList missingPrefixes = commandParameterSyntaxHandler.getMissingPrefixes(command, inputText); + + if (!missingPrefixes.isEmpty()) { + return missingPrefixes.get(0).getPrefix(); + } else { + return EMPTY_STRING; + } + + } +} diff --git a/src/main/java/seedu/address/logic/autocomplete/CommandParameterSyntaxHandler.java b/src/main/java/seedu/address/logic/autocomplete/CommandParameterSyntaxHandler.java new file mode 100644 index 000000000000..a38c00a07a92 --- /dev/null +++ b/src/main/java/seedu/address/logic/autocomplete/CommandParameterSyntaxHandler.java @@ -0,0 +1,117 @@ +package seedu.address.logic.autocomplete; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_COST; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_END; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_START; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ITEM_NUMBER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NEW_ROOM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROOM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.ArrayList; +import java.util.Arrays; + +import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.CheckInCommand; +import seedu.address.logic.commands.CheckoutCommand; +import seedu.address.logic.commands.LogInCommand; +import seedu.address.logic.commands.ReassignCommand; +import seedu.address.logic.commands.ServiceCommand; +import seedu.address.logic.parser.Prefix; + +/** + * Contains command Syntax definitions for multiple commands + */ +public class CommandParameterSyntaxHandler { + public static final ArrayList ADD_COMMAND_PREFIXES = getListOfPrefix(PREFIX_NAME, + PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ROOM, PREFIX_DATE_START, PREFIX_DATE_END, PREFIX_TAG); + + public static final ArrayList CHECKIN_COMMAND_PREFIXES = getListOfPrefix(PREFIX_ROOM); + + public static final ArrayList CHECKOUT_COMMAND_PREFIXES = getListOfPrefix(PREFIX_ROOM, + PREFIX_DATE_START, PREFIX_DATE_END); + + public static final ArrayList REASSIGN_COMMAND_PREFIXES = getListOfPrefix(PREFIX_ROOM, + PREFIX_DATE_START, PREFIX_NEW_ROOM); + + public static final ArrayList LOGIN_COMMAND_PREFIXES = getListOfPrefix(PREFIX_USERNAME, + PREFIX_PASSWORD); + + public static final ArrayList SERVICE_COMMAND_PREFIXES = getListOfPrefix(PREFIX_ROOM, + PREFIX_ITEM_NUMBER, PREFIX_COST); + + /** + * Returns ArrayList of prefixes from given prefixes + */ + private static ArrayList getListOfPrefix(Prefix... prefixes) { + return new ArrayList<>(Arrays.asList(prefixes)); + } + + /** + * Returns ArrayList of missing prefixes based on a String Command and current user text input + */ + public ArrayList getMissingPrefixes(String command, String input) { + ArrayList missingPrefixes = new ArrayList<>(); + + switch (command) { + + case AddCommand.COMMAND_WORD: + ADD_COMMAND_PREFIXES.forEach(prefix -> { + if (!input.contains(prefix.getPrefix())) { + missingPrefixes.add(prefix); + } + }); + break; + + case CheckInCommand.COMMAND_WORD: + CHECKIN_COMMAND_PREFIXES.forEach(prefix -> { + if (!input.contains(prefix.getPrefix())) { + missingPrefixes.add(prefix); + } + }); + break; + + case CheckoutCommand.COMMAND_WORD: + CHECKOUT_COMMAND_PREFIXES.forEach(prefix -> { + if (!input.contains(prefix.getPrefix())) { + missingPrefixes.add(prefix); + } + }); + break; + + case LogInCommand.COMMAND_WORD: + LOGIN_COMMAND_PREFIXES.forEach(prefix -> { + if (!input.contains(prefix.getPrefix())) { + missingPrefixes.add(prefix); + } + }); + break; + + case ReassignCommand.COMMAND_WORD: + REASSIGN_COMMAND_PREFIXES.forEach(prefix -> { + if (!input.contains(prefix.getPrefix())) { + missingPrefixes.add(prefix); + } + }); + break; + + case ServiceCommand.COMMAND_WORD: + SERVICE_COMMAND_PREFIXES.forEach(prefix -> { + if (!input.contains(prefix.getPrefix())) { + missingPrefixes.add(prefix); + } + }); + break; + + default: + break; + } + return missingPrefixes; + } + +} diff --git a/src/main/java/seedu/address/logic/autocomplete/Trie.java b/src/main/java/seedu/address/logic/autocomplete/Trie.java new file mode 100644 index 000000000000..7aad25d17c3d --- /dev/null +++ b/src/main/java/seedu/address/logic/autocomplete/Trie.java @@ -0,0 +1,114 @@ +package seedu.address.logic.autocomplete; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; + +/** + * Trie data structure for word auto-complete + */ +public class Trie { + + private Node root; + private int size = 0; + + /** + * Represents a node of trie + */ + private class Node { + private HashMap children = new HashMap<>(); + private boolean isCompleteWord = false; + } + + /** + * Creates a trie + */ + public Trie() { + root = new Node(); + } + + /** + * Inserts a word into Trie + */ + public void insertWord(String word) { + requireNonNull(word); + insert(root, word); + } + + /** + * Recursively insert to insert part of key into Trie + */ + private void insert(Node currentNode, String key) { + if (!key.isEmpty()) { + if (!currentNode.children.containsKey(key.charAt(0))) { + currentNode.children.put(key.charAt(0), new Node()); + } + insert(currentNode.children.get(key.charAt(0)), key.substring(1)); + } else { + if (!currentNode.isCompleteWord) { + size++; + } + currentNode.isCompleteWord = true; + } + } + + /** + * Auto-complete Strings + * Returns an ArrayList of strings of auto-completed words with given prefixes + */ + public List autoComplete(String prefix) { + List result = new ArrayList<>(); + if (search(root, prefix) == null) { + return result; + } + for (String s : getAllPostFix(search(root, prefix))) { + result.add(prefix + s); + } + return result; + } + + /** + * Recursive search for end node + */ + private Node search(Node currentNode, String key) { + if (!key.isEmpty() && currentNode != null) { + return search(currentNode.children.get(key.charAt(0)), key.substring(1)); + } else { + return currentNode; + } + } + + /** + * Returns ArrayList of all postfix from node + */ + private List getAllPostFix(Node node) { + ArrayList listOfPostFix = new ArrayList<>(); + return getAllPostFix(node, "", null, listOfPostFix); + } + + /** + * Recursive method to get all postfix string + */ + private List getAllPostFix(Node node, String s, Character next, List listOfPostFix) { + if (next != null) { + s += next; + } + for (Entry entry : node.children.entrySet()) { + listOfPostFix = getAllPostFix(entry.getValue(), s, entry.getKey(), listOfPostFix); + } + if (node.isCompleteWord) { + listOfPostFix.add(s); + } + return listOfPostFix; + } + + /** + * Returns size of Trie + */ + public int size() { + return size; + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index d88e831ff1ce..115af14ebc6f 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,69 +1,106 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_END; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_START; 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_ROOM; 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; +import seedu.address.model.guest.Guest; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.booking.Booking; +import seedu.address.model.room.booking.BookingPeriod; +import seedu.address.model.room.booking.exceptions.OverlappingBookingException; +import seedu.address.model.room.exceptions.RoomNotFoundException; + /** - * Adds a person to the address book. + * Adds a guest to Concierge. */ 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. " + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds a guest to the hotel and gives the guest a room. " + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_ROOM + " ROOM NUMBER " + + PREFIX_DATE_START + "dd/MM/yyyy or dd/MM/yy " + + PREFIX_DATE_END + "dd/MM/yyyy or dd/MM/yy " + "[" + 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"; + + PREFIX_TAG + "VIP " + + PREFIX_ROOM + "056 " + + PREFIX_DATE_START + "03/11/2018 " + + PREFIX_DATE_END + "05/11/2018"; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_SUCCESS = "Guest %1$s successfully made a booking for room: %2$s from %3$s"; + public static final String MESSAGE_OVERLAPPING_BOOKING = + "Cannot add booking, because it overlaps with another booking in room %s"; + public static final String MESSAGE_OUTDATED_BOOKING = + "Cannot add booking, because it starts on a past date"; - private final Person toAdd; + private final Guest guestToAdd; + private final RoomNumber roomNumberToAdd; + private final Booking bookingToAdd; /** - * Creates an AddCommand to add the specified {@code Person} + * Creates an AddCommand to add the specified {@code Guest}. + * The {@code guest} is assigned to {@code roomNumber} for the duration of + * {@code bookingPeriod}. */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; + public AddCommand(Guest guest, RoomNumber roomNumber, + BookingPeriod bookingPeriod) { + requireAllNonNull(guest, roomNumber, bookingPeriod); + guestToAdd = guest; + roomNumberToAdd = roomNumber; + bookingToAdd = new Booking(guest, bookingPeriod); } @Override public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + if (bookingToAdd.isOutdated()) { + throw new CommandException(MESSAGE_OUTDATED_BOOKING); + } + + try { + model.addBooking(roomNumberToAdd, bookingToAdd); + } catch (RoomNotFoundException e) { + throw new CommandException(e.getMessage()); + } catch (OverlappingBookingException e) { + throw new CommandException(String.format(MESSAGE_OVERLAPPING_BOOKING, roomNumberToAdd)); } - model.addPerson(toAdd); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + model.commitConcierge(); + return new CommandResult(String.format(MESSAGE_SUCCESS, guestToAdd, + roomNumberToAdd, bookingToAdd.getBookingPeriod())); + } + + @Override + public boolean requiresSignIn() { + return true; } @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)); + && guestToAdd.equals(((AddCommand) other).guestToAdd)); } } diff --git a/src/main/java/seedu/address/logic/commands/CheckInCommand.java b/src/main/java/seedu/address/logic/commands/CheckInCommand.java new file mode 100644 index 000000000000..5ba0ac221cd3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CheckInCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROOM; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.booking.exceptions.BookingAlreadyCheckedInException; +import seedu.address.model.room.booking.exceptions.ExpiredBookingException; +import seedu.address.model.room.booking.exceptions.InactiveBookingCheckInException; +import seedu.address.model.room.booking.exceptions.NoBookingException; + +/** + * Check in a room identified using its room number. + */ +public class CheckInCommand extends Command { + + public static final String COMMAND_WORD = "checkin"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Checks in the first active booking of the room identified by the room number.\n" + + Messages.MESSAGE_VALID_ROOM + + "Parameters: " + PREFIX_ROOM + "ROOM_NUMBER\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_ROOM + "r/001"; + + public static final String MESSAGE_CHECKIN_ROOM_SUCCESS = "Checked in Room: %1$s"; + public static final String MESSAGE_NO_BOOKING_CHECK_IN = + "Cannot check in Room %1$s, as it has no bookings."; + public static final String MESSAGE_EXPIRED_BOOKING_CHECK_IN = + "Cannot check in Room %1$s, as it has expired bookings."; + public static final String MESSAGE_INACTIVE_BOOKING_CHECK_IN = + "Cannot check in Room %1$s, as it does not have an active booking."; + public static final String MESSAGE_BOOKING_ALREADY_CHECKED_IN = + "Cannot check in Room %1$s, as it is already checked in."; + + private final RoomNumber roomNumber; + + public CheckInCommand(RoomNumber roomNumber) { + this.roomNumber = roomNumber; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + try { + model.checkInRoom(roomNumber); + model.commitConcierge(); + return new CommandResult(String.format(MESSAGE_CHECKIN_ROOM_SUCCESS, roomNumber)); + } catch (NoBookingException e) { + throw new CommandException(String.format(MESSAGE_NO_BOOKING_CHECK_IN, roomNumber)); + } catch (ExpiredBookingException e) { + throw new CommandException(String.format(MESSAGE_EXPIRED_BOOKING_CHECK_IN, roomNumber)); + } catch (InactiveBookingCheckInException e) { + throw new CommandException(String.format(MESSAGE_INACTIVE_BOOKING_CHECK_IN, roomNumber)); + } catch (BookingAlreadyCheckedInException e) { + throw new CommandException(String.format(MESSAGE_BOOKING_ALREADY_CHECKED_IN, roomNumber)); + } + } + + @Override + public boolean requiresSignIn() { + return true; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CheckInCommand // instanceof handles nulls + && roomNumber.equals(((CheckInCommand) other).roomNumber)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/CheckoutCommand.java b/src/main/java/seedu/address/logic/commands/CheckoutCommand.java new file mode 100644 index 000000000000..a5a90ba670e6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CheckoutCommand.java @@ -0,0 +1,91 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_START; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROOM; + +import java.time.LocalDate; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.events.ui.DeselectGuestListEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.model.Model; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.booking.exceptions.BookingNotFoundException; +import seedu.address.model.room.booking.exceptions.NoBookingException; + +/** + * Check out a room identified using its room number and remove its registered guest from the guest list. + */ +public class CheckoutCommand extends Command { + + public static final String COMMAND_WORD = "checkout"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Delete the booking of the room identified by the room number.\n" + + "You can opt to specify the start date of a booking to specify which booking to checkout (i.e. delete).\n" + + "If you do not specify a start date, the first (i.e. earliest) booking will be deleted.\n" + + Messages.MESSAGE_VALID_ROOM + + Messages.MESSAGE_VALID_DATE + + "Parameters: " + + PREFIX_ROOM + "ROOM_NUMBER " + + "[" + PREFIX_DATE_START + "START_DATE " + "]\n" + + "Example: " + + COMMAND_WORD + " " + + PREFIX_ROOM + "001 " + + PREFIX_DATE_START + "01/01/18"; + + public static final String MESSAGE_CHECKOUT_ROOM_SUCCESS = "Checked out Room: %1$s"; + public static final String MESSAGE_NO_ROOM_BOOKING = "Room %1$s has no bookings."; + public static final String MESSAGE_BOOKING_NOT_FOUND = "Room %1$s has no such bookings with start date %2$s."; + + private final RoomNumber roomNumber; + private final LocalDate startDate; + + public CheckoutCommand(RoomNumber roomNumber) { + this(roomNumber, null); + } + + public CheckoutCommand(RoomNumber roomNumber, LocalDate startDate) { + this.roomNumber = roomNumber; + this.startDate = startDate; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + try { + if (startDate == null) { + model.checkoutRoom(roomNumber); + } else { + model.checkoutRoom(roomNumber, startDate); + } + + EventsCenter.getInstance().post(new DeselectGuestListEvent()); + + model.commitConcierge(); + return new CommandResult(String.format(MESSAGE_CHECKOUT_ROOM_SUCCESS, roomNumber)); + + } catch (NoBookingException e) { + throw new CommandException(String.format(MESSAGE_NO_ROOM_BOOKING, roomNumber)); + } catch (BookingNotFoundException e) { + throw new CommandException(String.format(MESSAGE_BOOKING_NOT_FOUND, roomNumber, + ParserUtil.parseDateToString(startDate))); + } + } + + @Override + public boolean requiresSignIn() { + return true; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CheckoutCommand // instanceof handles nulls + && roomNumber.equals(((CheckoutCommand) other).roomNumber)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 1f85bcfe85a8..9b1399b394e5 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -1,25 +1,33 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.model.util.SampleDataUtil.getEmptyConcierge; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.DeselectGuestListEvent; import seedu.address.logic.CommandHistory; -import seedu.address.model.AddressBook; import seedu.address.model.Model; /** - * Clears the address book. + * Clears Concierge. */ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - + public static final String MESSAGE_SUCCESS = "Concierge has been cleared!"; @Override public CommandResult execute(Model model, CommandHistory history) { requireNonNull(model); - model.resetData(new AddressBook()); - model.commitAddressBook(); + model.resetData(getEmptyConcierge()); + EventsCenter.getInstance().post(new DeselectGuestListEvent()); + + model.commitConcierge(); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public boolean requiresSignIn() { + return true; + } } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 34e99d786ec6..36406fa0f3e3 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -19,4 +19,13 @@ public abstract class Command { */ public abstract CommandResult execute(Model model, CommandHistory history) throws CommandException; + /** + * Checks if a given command requires signing in to execute. + * The default value is false. + * + * A method is used instead of a field to prevent hiding. + */ + public boolean requiresSignIn() { + return false; + } } 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..796698407bc6 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -12,7 +12,7 @@ public class ExitCommand extends Command { 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 Concierge 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 index beb178e3a3f5..692c9b39443f 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -1,43 +1,102 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.FLAG_CHECKED_IN_GUEST; +import static seedu.address.logic.parser.CliSyntax.FLAG_GUEST; +import static seedu.address.logic.parser.CliSyntax.FLAG_ROOM; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.core.EventsCenter; import seedu.address.commons.core.Messages; +import seedu.address.commons.events.ui.ListingChangedEvent; import seedu.address.logic.CommandHistory; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.guest.Guest; +import seedu.address.model.room.Room; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. + * Finds and lists all rooms or guests in Concierge whose attributes matches + * those keywords that are specified by the user. 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"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all guests or rooms with the specified " + + "attributes and displays them as a list. There must be at least one attribute present.\n" + + "Parameters: GUEST/ROOMFLAG ATTRIBUTES [MORE ATTRIBUTES]...\n" + + "Example (Guest): " + COMMAND_WORD + " " + FLAG_GUEST + " n/Alex p/82542133 e/alex@gmail.com t/friend " + + "t/colleagues \n" + + "Example (Room): " + COMMAND_WORD + " " + FLAG_ROOM + " r/001 c/2 t/vacant -hb from/ 01/11/2018 to/ " + + "03/11/2018 \n"; + + private final List> guestPredicates; + private final List> roomPredicates; - private final NameContainsKeywordsPredicate predicate; + private final String flag; - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; + public FindCommand(String flag, List> guestPredicates, + List> roomPredicates) { + this.flag = flag; + this.guestPredicates = guestPredicates; + this.roomPredicates = roomPredicates; } @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())); + + if (flag.equals(FLAG_GUEST.toString())) { + Predicate combinedGuestPredicate = combineGuestFilters(); + model.updateFilteredGuestList(combinedGuestPredicate); + model.setDisplayedListFlag(FLAG_GUEST); + EventsCenter.getInstance().post(new ListingChangedEvent(FLAG_GUEST)); + + return new CommandResult( + String.format(Messages.MESSAGE_GUESTS_LISTED_OVERVIEW, model.getFilteredGuestList().size())); + + } else if (flag.equals(FLAG_CHECKED_IN_GUEST.toString())) { + Predicate combinedGuestPredicate = combineGuestFilters(); + model.updateFilteredCheckedInGuestList(combinedGuestPredicate); + model.setDisplayedListFlag(FLAG_CHECKED_IN_GUEST); + EventsCenter.getInstance().post(new ListingChangedEvent(FLAG_CHECKED_IN_GUEST)); + + return new CommandResult(String.format(Messages.MESSAGE_GUESTS_LISTED_OVERVIEW, + model.getFilteredCheckedInGuestList().size())); + } else { + Predicate combinedRoomPredicate = combineRoomFilters(); + model.updateFilteredRoomList(combinedRoomPredicate); + model.setDisplayedListFlag(FLAG_ROOM); + EventsCenter.getInstance().post(new ListingChangedEvent(FLAG_ROOM)); + + return new CommandResult( + String.format(Messages.MESSAGE_ROOMS_LISTED_OVERVIEW, model.getFilteredRoomList().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 + && guestPredicates.equals(((FindCommand) other).guestPredicates) + && roomPredicates.equals(((FindCommand) other).roomPredicates)); // state check + } + + /** + * Function to combine all the predicates in list of {@code guestPredicates}. + */ + public Predicate combineGuestFilters() { + Predicate predicates = guestPredicates.stream().reduce(Predicate::and).orElse(x-> true); + return predicates; + } + + /** + * Function to combine all the predicates in list of {@code roomPredicates}. + */ + public Predicate combineRoomFilters() { + Predicate predicates = roomPredicates.stream().reduce(Predicate::and).orElse(x-> true); + return predicates; } } diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 6d44824c7d1b..e7d7f1914d8d 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,25 +1,65 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.logic.parser.CliSyntax.FLAG_CHECKED_IN_GUEST; +import static seedu.address.logic.parser.CliSyntax.FLAG_GUEST; +import static seedu.address.logic.parser.CliSyntax.FLAG_ROOM; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.ListingChangedEvent; import seedu.address.logic.CommandHistory; +import seedu.address.logic.parser.Prefix; import seedu.address.model.Model; /** - * Lists all persons in the address book to the user. + * Lists all guests in Concierge to the user. */ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "List successful!"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows a list of guests or rooms. " + + "Parameters: " + + FLAG_GUEST + " for archived guests, " + + FLAG_CHECKED_IN_GUEST + " for checked-in guests. " + + FLAG_ROOM + " for rooms. \n" + + "Example: " + COMMAND_WORD + " " + + FLAG_ROOM; + + private final Prefix flag; + + /** + * Creates a ListCommand to handle listing of guests/rooms and other flags + */ + public ListCommand(Prefix flag) { + requireNonNull(flag); + this.flag = flag; + } @Override public CommandResult execute(Model model, CommandHistory history) { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + // flag is guaranteed to be valid by the parser (FLAG_GUEST, FLAG_ROOM, OR FLAG_CHECKED_IN_GUEST) + if (flag.equals(FLAG_GUEST)) { + model.updateFilteredGuestList(Model.PREDICATE_SHOW_ALL_GUESTS); + } else if (flag.equals(FLAG_ROOM)) { + model.updateFilteredRoomList(Model.PREDICATE_SHOW_ALL_ROOMS); + } else { // no need to check if here, because it is guaranteed to be FLAG_CHECKED_IN_GUEST at this stage + model.updateFilteredCheckedInGuestList(Model.PREDICATE_SHOW_ALL_GUESTS); + } + model.setDisplayedListFlag(flag); + EventsCenter.getInstance().post(new ListingChangedEvent(flag)); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ListCommand // instanceof handles nulls + && flag.equals(((ListCommand) other).flag)); + } + } 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..bbdf525606ae --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LogInCommand.java @@ -0,0 +1,68 @@ +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.login.InvalidLogInException; + +/** + * Allows the user to sign in to Concierge. The username and passwords are + * case-sensitive. + */ +public class LogInCommand extends Command { + + public static final String COMMAND_WORD = "login"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Signs in to Concierge. Both username and password are case-sensitive." + + "\nParameters: " + + PREFIX_USERNAME + "USERNAME " + + PREFIX_PASSWORD + "PASSWORD " + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_USERNAME + "admin " + + PREFIX_PASSWORD + "passw0rd "; + + public static final String MESSAGE_SUCCESS = "Successfully signed in as: %1$s"; + public static final String MESSAGE_SIGNED_IN_ALREADY = "You are already signed in."; + public static final String MESSAGE_NOT_SIGNED_IN = "This command requires you to sign in."; + + public final String userName; + private final String hashedPassword; + + /** + * Creates a LogInCommand to sign in as {@code userName} with password + * {@code hashedPassword}. + */ + public LogInCommand(String userName, String hashedPassword) { + this.userName = userName; + this.hashedPassword = hashedPassword; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.isSignedIn()) { + throw new CommandException(MESSAGE_SIGNED_IN_ALREADY); + } + + try { + model.signIn(userName, hashedPassword); + } catch (InvalidLogInException e) { + throw new CommandException(e.getMessage()); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, userName)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LogInCommand // instanceof handles nulls + && userName.equals(((LogInCommand) other).userName)); + } +} 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..769a51d1ca93 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LogOutCommand.java @@ -0,0 +1,33 @@ +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; +import seedu.address.model.login.InvalidLogOutException; + +/** + * Allows the user to sign out of Concierge. + * The command history is erased and the user cannot undo or redo beyond + * logout. + */ +public class LogOutCommand extends Command { + + public static final String COMMAND_WORD = "logout"; + public static final String MESSAGE_SUCCESS = "Successfully signed out of Concierge."; + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + try { + model.resetUndoRedoHistory(); + model.signOut(); + } catch (InvalidLogOutException e) { + throw new CommandException(e.getMessage()); + } + + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ReassignCommand.java b/src/main/java/seedu/address/logic/commands/ReassignCommand.java new file mode 100644 index 000000000000..4081e6fe7ad1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ReassignCommand.java @@ -0,0 +1,115 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_START; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NEW_ROOM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROOM; + +import java.time.LocalDate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.model.Model; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.booking.exceptions.BookingNotFoundException; +import seedu.address.model.room.booking.exceptions.ExpiredBookingException; +import seedu.address.model.room.booking.exceptions.NewBookingStartsBeforeOldBookingCheckedIn; +import seedu.address.model.room.booking.exceptions.OldBookingStartsBeforeNewBookingCheckedIn; +import seedu.address.model.room.booking.exceptions.OverlappingBookingException; +import seedu.address.model.room.exceptions.OriginalRoomReassignException; + +/** + * Reassigns a room's booking to another room. + */ +public class ReassignCommand extends Command { + + public static final String COMMAND_WORD = "reassign"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Reassigns a room's booking to another room.\n" + + Messages.MESSAGE_VALID_ROOM + + Messages.MESSAGE_VALID_DATE + + "Parameters: " + + PREFIX_ROOM + "ROOM_NUMBER " + + PREFIX_DATE_START + "START_DATE " + + PREFIX_NEW_ROOM + "NEW_ROOM_NUMBER\n" + + "Example: " + + COMMAND_WORD + " " + + PREFIX_ROOM + "001 " + + PREFIX_DATE_START + "01/01/18 " + + PREFIX_NEW_ROOM + "002"; + + public static final String MESSAGE_REASSIGN_SUCCESS = + "Reassigned booking with start date %1$s from room %2$s to room %3$s."; + public static final String MESSAGE_BOOKING_NOT_FOUND = "Room %1$s has no such bookings with start date %2$s."; + public static final String MESSAGE_REASSIGN_TO_ORIGINAL_ROOM = + "Cannot reassign booking to its original room."; + public static final String MESSAGE_EXPIRED_BOOKING_REASSIGN = + "Cannot reassign booking, because either it or the new room's first booking is expired."; + public static final String MESSAGE_OVERLAPPING_BOOKING = + "Cannot reassign booking, because it overlaps with another booking in room %s."; + public static final String MESSAGE_BOOKING_STARTS_BEFORE_NEW_BOOKING_CHECKED_IN = + "Cannot reassign booking, because this booking starts before new room's booking " + + "and new room's booking is already checked-in."; + public static final String MESSAGE_NEW_BOOKING_STARTS_BEFORE_BOOKING_CHECKED_IN = + "Cannot reassign booking, because the new room's booking starts before this booking " + + "and this booking is already checked-in."; + + private final RoomNumber roomNumber; + private final LocalDate startDate; + private final RoomNumber newRoomNumber; + + public ReassignCommand(RoomNumber roomNumber, LocalDate startDate, RoomNumber newRoomNumber) { + this.roomNumber = roomNumber; + this.startDate = startDate; + this.newRoomNumber = newRoomNumber; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + try { + model.reassignRoom(roomNumber, startDate, newRoomNumber); + model.commitConcierge(); + return new CommandResult(String.format(MESSAGE_REASSIGN_SUCCESS, + ParserUtil.parseDateToString(startDate), roomNumber, newRoomNumber)); + + } catch (OriginalRoomReassignException e) { + throw new CommandException(MESSAGE_REASSIGN_TO_ORIGINAL_ROOM); + + } catch (BookingNotFoundException e) { + throw new CommandException(String.format(MESSAGE_BOOKING_NOT_FOUND, roomNumber, + ParserUtil.parseDateToString(startDate))); + + } catch (ExpiredBookingException e) { + throw new CommandException(MESSAGE_EXPIRED_BOOKING_REASSIGN); + + } catch (OldBookingStartsBeforeNewBookingCheckedIn e) { + throw new CommandException(MESSAGE_BOOKING_STARTS_BEFORE_NEW_BOOKING_CHECKED_IN); + + } catch (NewBookingStartsBeforeOldBookingCheckedIn e) { + throw new CommandException(MESSAGE_NEW_BOOKING_STARTS_BEFORE_BOOKING_CHECKED_IN); + + } catch (OverlappingBookingException e) { + throw new CommandException(String.format(MESSAGE_OVERLAPPING_BOOKING, newRoomNumber)); + + } + } + + @Override + public boolean requiresSignIn() { + return true; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReassignCommand // instanceof handles nulls + && roomNumber.equals(((ReassignCommand) other).roomNumber)) // state check + && startDate.equals(((ReassignCommand) other).startDate) + && newRoomNumber.equals(((ReassignCommand) other).newRoomNumber); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 227771a4eef6..c48a8e140465 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -1,14 +1,17 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GUESTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ROOMS; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.DeselectGuestListEvent; 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. + * Reverts the {@code model}'s Concierge to its previously undone state. */ public class RedoCommand extends Command { @@ -20,12 +23,16 @@ public class RedoCommand extends Command { public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - if (!model.canRedoAddressBook()) { + if (!model.canRedoConcierge()) { throw new CommandException(MESSAGE_FAILURE); } - model.redoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + EventsCenter.getInstance().post(new DeselectGuestListEvent()); + + model.redoConcierge(); + model.updateFilteredGuestList(PREDICATE_SHOW_ALL_GUESTS); + model.updateFilteredCheckedInGuestList(PREDICATE_SHOW_ALL_GUESTS); + model.updateFilteredRoomList(PREDICATE_SHOW_ALL_ROOMS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java index f5e8c1a8722e..8c7b7850d0e2 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/address/logic/commands/SelectCommand.java @@ -10,22 +10,27 @@ import seedu.address.commons.events.ui.JumpToListRequestEvent; import seedu.address.logic.CommandHistory; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.CliSyntax; +import seedu.address.logic.parser.Prefix; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.guest.Guest; +import seedu.address.model.room.Room; /** - * Selects a person identified using it's displayed index from the address book. + * Selects a guest identified using it's displayed index from Concierge. */ public class SelectCommand extends Command { 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" + + ": Selects the guest identified by the index number used in the displayed guest list.\n" + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1"; - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; + public static final String MESSAGE_SELECT_GUEST_SUCCESS = "Selected Guest: %1$s"; + public static final String MESSAGE_SELECT_CHECKED_IN_GUEST_SUCCESS = "Selected Checked-in Guest: %1$s"; + public static final String MESSAGE_SELECT_ROOM_SUCCESS = "Selected Room: %1$s"; private final Index targetIndex; @@ -37,14 +42,39 @@ public SelectCommand(Index targetIndex) { public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - List filteredPersonList = model.getFilteredPersonList(); + Prefix displayedListFlag = model.getDisplayedListFlag(); + String successMessage; - if (targetIndex.getZeroBased() >= filteredPersonList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + if (displayedListFlag.equals(CliSyntax.FLAG_GUEST)) { + List filteredGuestList = model.getFilteredGuestList(); + + if (targetIndex.getZeroBased() >= filteredGuestList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GUEST_DISPLAYED_INDEX); + } + successMessage = MESSAGE_SELECT_GUEST_SUCCESS; + + } else if (displayedListFlag.equals(CliSyntax.FLAG_CHECKED_IN_GUEST)) { + List filteredCheckedInGuestList = model.getFilteredCheckedInGuestList(); + + if (targetIndex.getZeroBased() >= filteredCheckedInGuestList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_CHECKED_IN_GUEST_DISPLAYED_INDEX); + } + successMessage = MESSAGE_SELECT_CHECKED_IN_GUEST_SUCCESS; + + } else if (displayedListFlag.equals(CliSyntax.FLAG_ROOM)) { + List filteredRoomList = model.getFilteredRoomList(); + + if (targetIndex.getZeroBased() >= filteredRoomList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ROOM_DISPLAYED_INDEX); + } + successMessage = MESSAGE_SELECT_ROOM_SUCCESS; + + } else { + throw new CommandException(Messages.MESSAGE_NO_LIST_DISPLAYED); } - EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); + EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex, displayedListFlag)); + return new CommandResult(String.format(successMessage, targetIndex.getOneBased())); } diff --git a/src/main/java/seedu/address/logic/commands/ServiceCommand.java b/src/main/java/seedu/address/logic/commands/ServiceCommand.java new file mode 100644 index 000000000000..6654b2a46313 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ServiceCommand.java @@ -0,0 +1,99 @@ +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_COST; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ITEM_NUMBER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROOM; + +import java.util.Optional; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Menu; +import seedu.address.model.Model; +import seedu.address.model.expenses.Expense; +import seedu.address.model.expenses.Money; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.booking.exceptions.NoBookingException; +import seedu.address.model.room.booking.exceptions.RoomNotCheckedInException; + +/** + * Adds an Expense to a Room. + */ +public class ServiceCommand extends Command { + + public static final String COMMAND_WORD = "service"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds an Expense for the guests of a Room. " + + "Parameters: " + + PREFIX_ROOM + "ROOM " + + PREFIX_ITEM_NUMBER + "ITEM NUMBER " + + "[" + PREFIX_COST + "COST]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_ROOM + "001 " + + PREFIX_ITEM_NUMBER + "RS01 " + + PREFIX_COST + "2.45"; + + public static final String MESSAGE_SUCCESS = + "New expense added: %1$s\nCharged to room: %2$s"; + public static final String MESSAGE_ROOM_HAS_NO_GUEST = + "There are no guests checked in to this room."; + public static final String MESSAGE_ITEM_NOT_FOUND = + "The item was not found in the menu."; + + private final RoomNumber roomNumber; + private final String itemNumber; + private final Optional itemCost; + + /** + * Creates a command to add the Expense to the Room. + * @param roomNumber The number of the room to add the expense to + * @param itemNumber The menu number of the item bought + * @param itemCost The cost of the item + */ + public ServiceCommand(RoomNumber roomNumber, String itemNumber, Optional itemCost) { + requireAllNonNull(roomNumber, itemNumber, itemCost); + this.roomNumber = roomNumber; + this.itemNumber = itemNumber; + this.itemCost = itemCost; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + Menu menu = model.getMenu(); + if (!menu.isValidMenuNumber(itemNumber)) { + throw new CommandException(MESSAGE_ITEM_NOT_FOUND); + } + Expense expense; + if (itemCost.isPresent()) { + expense = new Expense(menu.getExpenseType(itemNumber), itemCost.get()); + } else { + expense = new Expense(menu.getExpenseType(itemNumber)); + } + try { + model.addExpense(roomNumber, expense); + model.commitConcierge(); + return new CommandResult(String.format(MESSAGE_SUCCESS, + expense.getItemName(), roomNumber.toString())); + } catch (NoBookingException | RoomNotCheckedInException e) { + throw new CommandException(MESSAGE_ROOM_HAS_NO_GUEST); + } + } + + @Override + public boolean requiresSignIn() { + return true; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ServiceCommand // instanceof handles nulls + && roomNumber.equals(((ServiceCommand) other).roomNumber) + && itemNumber.equals(((ServiceCommand) other).itemNumber) + && itemCost.equals(((ServiceCommand) other).itemCost)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 40441264f346..f2137b00ecbe 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -1,14 +1,17 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GUESTS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ROOMS; +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.DeselectGuestListEvent; 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. + * Reverts the {@code model}'s Concierge to its previous state. */ public class UndoCommand extends Command { @@ -20,12 +23,16 @@ public class UndoCommand extends Command { public CommandResult execute(Model model, CommandHistory history) throws CommandException { requireNonNull(model); - if (!model.canUndoAddressBook()) { + if (!model.canUndoConcierge()) { throw new CommandException(MESSAGE_FAILURE); } - model.undoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + EventsCenter.getInstance().post(new DeselectGuestListEvent()); + + model.undoConcierge(); + model.updateFilteredGuestList(PREDICATE_SHOW_ALL_GUESTS); + model.updateFilteredCheckedInGuestList(PREDICATE_SHOW_ALL_GUESTS); + model.updateFilteredRoomList(PREDICATE_SHOW_ALL_ROOMS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java index a16bd14f2cde..f44c0c191868 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java @@ -1,5 +1,7 @@ package seedu.address.logic.commands.exceptions; +import seedu.address.logic.commands.Command; + /** * Represents an error which occurs during execution of a {@link Command}. */ diff --git a/src/main/java/seedu/address/logic/commands/exceptions/UnauthorisedCommandException.java b/src/main/java/seedu/address/logic/commands/exceptions/UnauthorisedCommandException.java new file mode 100644 index 000000000000..c833e16df71f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/exceptions/UnauthorisedCommandException.java @@ -0,0 +1,14 @@ +package seedu.address.logic.commands.exceptions; + +import seedu.address.logic.commands.LogInCommand; + +/** + * Represents the exception where the user is not authorised to execute the + * command. That is, the user needs to sign in but is not signed in. + */ +public class UnauthorisedCommandException extends CommandException { + + public UnauthorisedCommandException() { + super(LogInCommand.MESSAGE_NOT_SIGNED_IN); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e83..cb36cc70a63b 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,22 +1,24 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_END; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_START; 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_ROOM; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Set; -import java.util.stream.Stream; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.guest.Email; +import seedu.address.model.guest.Guest; +import seedu.address.model.guest.Name; +import seedu.address.model.guest.Phone; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.booking.BookingPeriod; import seedu.address.model.tag.Tag; /** @@ -31,9 +33,13 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, + PREFIX_EMAIL, PREFIX_TAG, PREFIX_ROOM, + PREFIX_DATE_START, PREFIX_DATE_END); + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_NAME, + PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ROOM, + PREFIX_DATE_START, + PREFIX_DATE_END) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } @@ -41,20 +47,16 @@ public AddCommand parse(String args) throws ParseException { Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } + RoomNumber roomNumber = + ParserUtil.parseRoomNumber(argMultimap.getValue(PREFIX_ROOM).get()); + BookingPeriod bookingPeriod = + ParserUtil.parseBookingPeriod(argMultimap.getValue(PREFIX_DATE_START).get(), + argMultimap.getValue(PREFIX_DATE_END).get()); + Guest guest = new Guest(name, phone, email, tagList); - /** - * 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(guest, roomNumber, bookingPeriod); } } diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java index 954c8e18f8ea..d0d0fe5af5ba 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -57,4 +57,11 @@ public List getAllValues(Prefix prefix) { public String getPreamble() { return getValue(new Prefix("")).orElse(""); } + + /** + * Returns the number of prefix-argument mappings of this ArgumentMultimap + */ + public int getSize() { + return argMultimap.size(); + } } diff --git a/src/main/java/seedu/address/logic/parser/CheckInCommandParser.java b/src/main/java/seedu/address/logic/parser/CheckInCommandParser.java new file mode 100644 index 000000000000..b079f68e78c5 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CheckInCommandParser.java @@ -0,0 +1,35 @@ +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_ROOM; + +import seedu.address.logic.commands.CheckInCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.room.RoomNumber; + +/** + * Parses input arguments and creates a new CheckInCommand object + */ +public class CheckInCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CheckInCommand + * and returns an CheckInCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CheckInCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_ROOM); + if (!argMultimap.getValue(PREFIX_ROOM).isPresent() || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CheckInCommand.MESSAGE_USAGE)); + } + try { + RoomNumber roomNumber = ParserUtil.parseRoomNumber(argMultimap.getValue(PREFIX_ROOM).get()); + return new CheckInCommand(roomNumber); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CheckInCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/CheckoutCommandParser.java b/src/main/java/seedu/address/logic/parser/CheckoutCommandParser.java new file mode 100644 index 000000000000..5863cb829f82 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CheckoutCommandParser.java @@ -0,0 +1,44 @@ +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_DATE_START; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROOM; + +import java.time.LocalDate; +import java.util.Optional; + +import seedu.address.logic.commands.CheckoutCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.room.RoomNumber; + +/** + * Parses input arguments and creates a new CheckoutCommand object + */ +public class CheckoutCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CheckoutCommand + * and returns an CheckoutCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CheckoutCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_ROOM, PREFIX_DATE_START); + if (!argMultimap.getValue(PREFIX_ROOM).isPresent() || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CheckoutCommand.MESSAGE_USAGE)); + } + try { + RoomNumber roomNumber = ParserUtil.parseRoomNumber(argMultimap.getValue(PREFIX_ROOM).get()); + Optional optionalStartDate = argMultimap.getValue(PREFIX_DATE_START); + if (optionalStartDate.isPresent()) { + LocalDate startDate = ParserUtil.parseDate(optionalStartDate.get()); + return new CheckoutCommand(roomNumber, startDate); + } + return new CheckoutCommand(roomNumber); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CheckoutCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..ab4d704b1713 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -5,11 +5,34 @@ */ public class CliSyntax { - /* Prefix definitions */ + /* Identifier prefixes */ + // guest public static final Prefix PREFIX_NAME = new Prefix("n/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + // room + public static final Prefix PREFIX_ROOM = new Prefix("r/"); + public static final Prefix PREFIX_ROOM_CAPACITY = new Prefix("c/"); + public static final Prefix PREFIX_NEW_ROOM = new Prefix("nr/"); + + // booking + public static final Prefix PREFIX_DATE_START = new Prefix("from/"); + public static final Prefix PREFIX_DATE_END = new Prefix("to/"); + + // expense + public static final Prefix PREFIX_ITEM_NUMBER = new Prefix("no/"); + public static final Prefix PREFIX_COST = new Prefix("c/"); + + /* Flag prefixes */ + public static final Prefix FLAG_ROOM = new Prefix("-r"); + public static final Prefix FLAG_GUEST = new Prefix("-g"); + public static final Prefix FLAG_ROOM_HAS_BOOKINGS = new Prefix("-hb"); + public static final Prefix FLAG_ROOM_NO_BOOKINGS = new Prefix("-nb"); + public static final Prefix FLAG_CHECKED_IN_GUEST = new Prefix("-cg"); + + /* Login prefixes */ + public static final Prefix PREFIX_USERNAME = new Prefix("user/"); + public static final Prefix PREFIX_PASSWORD = new Prefix("pw/"); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/ConciergeParser.java similarity index 72% rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java rename to src/main/java/seedu/address/logic/parser/ConciergeParser.java index b7d57f5db86a..545611f1b5d0 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/ConciergeParser.java @@ -7,24 +7,28 @@ import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.CheckInCommand; +import seedu.address.logic.commands.CheckoutCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.LogInCommand; +import seedu.address.logic.commands.LogOutCommand; +import seedu.address.logic.commands.ReassignCommand; import seedu.address.logic.commands.RedoCommand; import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.ServiceCommand; import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.parser.exceptions.ParseException; /** * Parses user input. */ -public class AddressBookParser { +public class ConciergeParser { /** * Used for initial separation of command word and args. @@ -51,15 +55,18 @@ public Command parseCommand(String userInput) throws ParseException { case AddCommand.COMMAND_WORD: return new AddCommandParser().parse(arguments); - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); + case ReassignCommand.COMMAND_WORD: + return new ReassignCommandParser().parse(arguments); + + case CheckInCommand.COMMAND_WORD: + return new CheckInCommandParser().parse(arguments); + + case CheckoutCommand.COMMAND_WORD: + return new CheckoutCommandParser().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(); @@ -67,7 +74,16 @@ public Command parseCommand(String userInput) throws ParseException { return new FindCommandParser().parse(arguments); case ListCommand.COMMAND_WORD: - return new ListCommand(); + return new ListCommandParser().parse(arguments); + + case ServiceCommand.COMMAND_WORD: + return new ServiceCommandParser().parse(arguments); + + case LogInCommand.COMMAND_WORD: + return new LogInCommandParser().parse(arguments); + + case LogOutCommand.COMMAND_WORD: + return new LogOutCommand(); case HistoryCommand.COMMAND_WORD: return new HistoryCommand(); diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 4d1f4bb0e4ec..000000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns an DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 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 index b186a967cb94..b05fe92f0022 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,33 +1,243 @@ package seedu.address.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.FLAG_CHECKED_IN_GUEST; +import static seedu.address.logic.parser.CliSyntax.FLAG_GUEST; +import static seedu.address.logic.parser.CliSyntax.FLAG_ROOM; +import static seedu.address.logic.parser.CliSyntax.FLAG_ROOM_HAS_BOOKINGS; +import static seedu.address.logic.parser.CliSyntax.FLAG_ROOM_NO_BOOKINGS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_END; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_START; +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_ROOM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROOM_CAPACITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Predicate; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; + +import seedu.address.model.guest.Guest; +import seedu.address.model.guest.GuestEmailExactPredicate; +import seedu.address.model.guest.GuestNameContainsKeywordsPredicate; +import seedu.address.model.guest.GuestPhoneExactPredicate; +import seedu.address.model.guest.GuestTagsContainsKeywordsPredicate; +import seedu.address.model.guest.Name; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomBookingsDateRangePredicate; +import seedu.address.model.room.RoomCapacityExactPredicate; +import seedu.address.model.room.RoomHasBookingWithGuestExactPredicate; +import seedu.address.model.room.RoomHasBookingsExactPredicate; +import seedu.address.model.room.RoomNumberExactPredicate; +import seedu.address.model.room.RoomTagsContainsKeywordsPredicate; +import seedu.address.model.room.booking.BookingPeriod; +import seedu.address.model.tag.Tag; /** * Parses input arguments and creates a new FindCommand object */ public class FindCommandParser implements Parser { + public static final String MESSAGE_NO_FLAGS = "No flags found! \n%1$s"; + public static final String MESSAGE_NO_FILTERS = "No specified filters found! \n%1$s"; + public static final String MESSAGE_NULL_FILTERS = "Null value in filters found! \n%1$s"; + public static final String MESSAGE_EXTRA_BOOKING_FLAG = "Extra bookings flag found! \n%1$s"; + public static final String MESSAGE_BOOKING_PERIOD_FORMAT = "Booking period format is wrong! \n%1$s"; + public static final String MESSAGE_BOOKING_SAME_DATE = "Specified days should not be the same! \n%1$s"; + + private final List> guestPredicates; + private final List> roomPredicates; + + public FindCommandParser() { + guestPredicates = new LinkedList<>(); + roomPredicates = new LinkedList<>(); + } + /** * 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()) { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, FLAG_ROOM, FLAG_GUEST, FLAG_CHECKED_IN_GUEST); + + if ((!ParserUtil.arePrefixesPresent(argMultimap, FLAG_ROOM) + && !ParserUtil.arePrefixesPresent(argMultimap, FLAG_GUEST) + && !ParserUtil.arePrefixesPresent(argMultimap, FLAG_CHECKED_IN_GUEST)) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_NO_FLAGS, FindCommand.MESSAGE_USAGE)); + } + + if (ParserUtil.arePrefixesPresent(argMultimap, FLAG_GUEST)) { + getGuestPredicates(args); + return new FindCommand(FLAG_GUEST.toString(), guestPredicates, roomPredicates); + } else if (ParserUtil.arePrefixesPresent(argMultimap, FLAG_CHECKED_IN_GUEST)) { + getGuestPredicates(args); + return new FindCommand(FLAG_CHECKED_IN_GUEST.toString(), guestPredicates, roomPredicates); + } else { + getRoomPredicates(args); + return new FindCommand(FLAG_ROOM.toString(), guestPredicates, roomPredicates); + } + } + + /** + * Handles the logic and creation of several predicates based on specified prefixes/flags by the user from + * {@code suffixFilters} to find guests. + * @throws ParseException if the user input does not conform the expected format + */ + private void getGuestPredicates(String args) throws ParseException, IllegalArgumentException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, + PREFIX_EMAIL, PREFIX_TAG); + + if (!ParserUtil.areAnyPrefixPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, + PREFIX_EMAIL, PREFIX_TAG)) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + String.format(MESSAGE_NO_FILTERS, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + if (ParserUtil.areAnyPrefixValueNull(argMultimap, PREFIX_NAME, PREFIX_PHONE, + PREFIX_EMAIL, PREFIX_TAG)) { + throw new ParseException( + String.format(MESSAGE_NULL_FILTERS, FindCommand.MESSAGE_USAGE)); + } + + if (ParserUtil.arePrefixesPresent(argMultimap, PREFIX_NAME)) { + String[] nameKeywords = argMultimap.getValue(PREFIX_NAME).get().trim().split("\\s+"); + List names = new LinkedList<>(); + + for (String name : nameKeywords) { + names.add(ParserUtil.parseName(name)); + } + + guestPredicates.add(new GuestNameContainsKeywordsPredicate(names)); + } + + if (ParserUtil.arePrefixesPresent(argMultimap, PREFIX_PHONE)) { + guestPredicates.add(new GuestPhoneExactPredicate( + ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()))); + } - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + if (ParserUtil.arePrefixesPresent(argMultimap, PREFIX_EMAIL)) { + guestPredicates.add(new GuestEmailExactPredicate( + ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()))); + } + + if (ParserUtil.arePrefixesPresent(argMultimap, PREFIX_TAG)) { + String[] tagsKeywords = argMultimap.getValue(PREFIX_TAG).get().trim().split("\\s+"); + List tags = new LinkedList<>(); + + for (String tag : tagsKeywords) { + tags.add(ParserUtil.parseTag(tag)); + } + + guestPredicates.add(new GuestTagsContainsKeywordsPredicate(tags)); + } } + /** + * Handles the logic and creation of several predicates based on specified prefixes/flags by the user from + * {@code suffixFilters} to find rooms. + * @throws ParseException if the user input does not conform the expected format + */ + private void getRoomPredicates(String args) throws ParseException { + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_ROOM, PREFIX_ROOM_CAPACITY, + PREFIX_TAG, FLAG_ROOM_HAS_BOOKINGS, FLAG_ROOM_NO_BOOKINGS, PREFIX_DATE_START, + PREFIX_DATE_END, PREFIX_NAME); + + if (!ParserUtil.areAnyPrefixPresent(argMultimap, PREFIX_ROOM, PREFIX_ROOM_CAPACITY, + PREFIX_TAG, FLAG_ROOM_HAS_BOOKINGS, FLAG_ROOM_NO_BOOKINGS, PREFIX_DATE_START, + PREFIX_DATE_END, PREFIX_NAME)) { + throw new ParseException( + String.format(MESSAGE_NO_FILTERS, FindCommand.MESSAGE_USAGE)); + } + + if (ParserUtil.arePrefixesPresent(argMultimap, FLAG_ROOM_HAS_BOOKINGS, FLAG_ROOM_NO_BOOKINGS)) { + throw new ParseException( + String.format(MESSAGE_EXTRA_BOOKING_FLAG, FindCommand.MESSAGE_USAGE)); + } + + if (ParserUtil.areAnyPrefixPresent(argMultimap, PREFIX_DATE_START, PREFIX_DATE_END)) { + if (!ParserUtil.areAnyPrefixPresent(argMultimap, FLAG_ROOM_HAS_BOOKINGS, FLAG_ROOM_NO_BOOKINGS)) { + throw new ParseException( + String.format(MESSAGE_BOOKING_PERIOD_FORMAT, FindCommand.MESSAGE_USAGE)); + } + } + + if (ParserUtil.areAnyPrefixValueNull(argMultimap, PREFIX_ROOM, PREFIX_ROOM_CAPACITY, + PREFIX_TAG, PREFIX_DATE_START, PREFIX_DATE_END, PREFIX_NAME)) { + throw new ParseException( + String.format(MESSAGE_NULL_FILTERS, FindCommand.MESSAGE_USAGE)); + } + + + if (ParserUtil.arePrefixesPresent(argMultimap, FLAG_ROOM_HAS_BOOKINGS) + || ParserUtil.arePrefixesPresent(argMultimap, FLAG_ROOM_NO_BOOKINGS)) { + if (ParserUtil.arePrefixesPresent(argMultimap, PREFIX_DATE_START) + || ParserUtil.arePrefixesPresent(argMultimap, PREFIX_DATE_END)) { + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_DATE_START, PREFIX_DATE_END)) { + throw new ParseException( + String.format(MESSAGE_BOOKING_PERIOD_FORMAT, FindCommand.MESSAGE_USAGE)); + } + } + } + + if (ParserUtil.arePrefixesPresent(argMultimap, PREFIX_ROOM)) { + roomPredicates.add(new RoomNumberExactPredicate( + ParserUtil.parseRoomNumber(argMultimap.getValue(PREFIX_ROOM).get()))); + } + + if (ParserUtil.arePrefixesPresent(argMultimap, PREFIX_ROOM_CAPACITY)) { + roomPredicates.add(new RoomCapacityExactPredicate( + ParserUtil.parseCapacity(argMultimap.getValue(PREFIX_ROOM_CAPACITY).get()))); + } + + if (ParserUtil.arePrefixesPresent(argMultimap, PREFIX_TAG)) { + String[] tagsKeywords = argMultimap.getValue(PREFIX_TAG).get().trim().split("\\s+"); + List tags = new LinkedList<>(); + + for (String tag : tagsKeywords) { + tags.add(ParserUtil.parseTag(tag)); + } + roomPredicates.add(new RoomTagsContainsKeywordsPredicate(tags)); + } + + if (ParserUtil.arePrefixesPresent(argMultimap, FLAG_ROOM_HAS_BOOKINGS)) { + roomPredicates.add(new RoomHasBookingsExactPredicate(true)); + } else if (ParserUtil.arePrefixesPresent(argMultimap, FLAG_ROOM_NO_BOOKINGS)) { + roomPredicates.add(new RoomHasBookingsExactPredicate(false)); + } + + if (ParserUtil.arePrefixesPresent(argMultimap, PREFIX_DATE_START)) { + String startDate = argMultimap.getValue(PREFIX_DATE_START).get(); + String endDate = argMultimap.getValue(PREFIX_DATE_END).get(); + + try { + roomPredicates.add(new RoomBookingsDateRangePredicate(new BookingPeriod(startDate, endDate))); + } catch (IllegalArgumentException e) { + throw new ParseException( + String.format(MESSAGE_BOOKING_SAME_DATE, FindCommand.MESSAGE_USAGE)); + } + } + + if (ParserUtil.arePrefixesPresent(argMultimap, PREFIX_NAME)) { + String[] nameKeywords = argMultimap.getValue(PREFIX_NAME).get().trim().split("\\s+"); + List names = new LinkedList<>(); + + for (String name : nameKeywords) { + names.add(ParserUtil.parseName(name)); + } + + roomPredicates.add(new RoomHasBookingWithGuestExactPredicate(names)); + } + } } diff --git a/src/main/java/seedu/address/logic/parser/ListCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCommandParser.java new file mode 100644 index 000000000000..bb13b050cbbd --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java @@ -0,0 +1,37 @@ +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.FLAG_CHECKED_IN_GUEST; +import static seedu.address.logic.parser.CliSyntax.FLAG_GUEST; +import static seedu.address.logic.parser.CliSyntax.FLAG_ROOM; + +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new ListCommand object + */ + +public class ListCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ListCommand + * and returns an ListCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ListCommand parse(String args) throws ParseException { + requireNonNull(args); + String trimmedArgs = args.trim(); + if (trimmedArgs.equals(FLAG_GUEST.toString())) { + return new ListCommand(FLAG_GUEST); + } else if (trimmedArgs.equals(FLAG_ROOM.toString())) { + return new ListCommand(FLAG_ROOM); + } else if (trimmedArgs.equals(FLAG_CHECKED_IN_GUEST.toString())) { + return new ListCommand(FLAG_CHECKED_IN_GUEST); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + } + +} 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..f02b4e7492f3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/LogInCommandParser.java @@ -0,0 +1,40 @@ +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; + +/** + * Parses input arguments and creates a new LogInCommand object + */ + +public class LogInCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the + * LogInCommand and returns a LogInCommand object for execution. + * + * The password supplied will be hashed immediately, so sensitive data is + * immediately handled. + * @throws ParseException if the user input does not conform the expected format. + */ + 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)); + } + + String userName = argMultimap.getValue(PREFIX_USERNAME).get(); + String hashedPasswordGuess = + ParserUtil.parseAndHashPassword(argMultimap.getValue(PREFIX_PASSWORD).get()); + + return new LogInCommand(userName, hashedPasswordGuess); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 76daf40807e2..6ff53444082b 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,17 +2,25 @@ import static java.util.Objects.requireNonNull; +import java.time.DateTimeException; +import java.time.LocalDate; import java.util.Collection; import java.util.HashSet; +import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; +import seedu.address.commons.core.Messages; 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.expenses.Money; +import seedu.address.model.guest.Email; +import seedu.address.model.guest.Name; +import seedu.address.model.guest.Phone; +import seedu.address.model.room.Capacity; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.booking.BookingPeriod; import seedu.address.model.tag.Tag; /** @@ -20,8 +28,6 @@ */ public class ParserUtil { - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; - /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. @@ -30,7 +36,7 @@ public class ParserUtil { public static Index parseIndex(String oneBasedIndex) throws ParseException { String trimmedIndex = oneBasedIndex.trim(); if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); + throw new ParseException(Index.MESSAGE_INDEX_CONSTRAINTS); } return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } @@ -65,21 +71,6 @@ public static Phone parsePhone(String phone) throws ParseException { return new Phone(trimmedPhone); } - /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code address} is invalid. - */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_ADDRESS_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - /** * Parses a {@code String email} into an {@code Email}. * Leading and trailing whitespaces will be trimmed. @@ -121,4 +112,126 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String roomNumber} into a {@code RoomNumber}. + * Leading and trailing whitespaces will be trimmed. + * @throws ParseException if the given {@code roomNumber} is invalid. + */ + public static RoomNumber parseRoomNumber(String roomNumber) throws ParseException { + requireNonNull(roomNumber); + String trimmedRoomNumber = roomNumber.trim(); + if (!RoomNumber.isValidRoomNumber(trimmedRoomNumber)) { + throw new ParseException(RoomNumber.MESSAGE_ROOM_NUMBER_CONSTRAINTS); + } + return new RoomNumber(trimmedRoomNumber); + } + + /** + * Parses a {@code String capacity} into a {@code Capacity}. + * Leading and trailing whitespaces will be trimmed. + * @throws ParseException if the given {@code capacity} is invalid. + */ + public static String parseCapacity(String capacity) throws ParseException { + requireNonNull(capacity); + String trimmedCapacity = capacity.trim(); + if (!Capacity.isValidCapacity(trimmedCapacity)) { + throw new ParseException(Capacity.MESSAGE_CAPACITY_CONSTRAINTS); + } + return capacity; + } + + /** + * Parses a {@code String startDate} and {@code String endDate} into a + * {@code BookingPeriod}. + * Leading and trailing whitespaces will be trimmed. + * @throws ParseException if either of the given {@code startDate} or + * {@code endDate} is invalid. + */ + public static BookingPeriod parseBookingPeriod(String startDate, + String endDate) throws ParseException { + requireNonNull(startDate, endDate); + String trimmedStartDate = startDate.trim(); + String trimmedEndDate = endDate.trim(); + if (!BookingPeriod.isValidBookingPeriod(trimmedStartDate, trimmedEndDate)) { + throw new ParseException(BookingPeriod.MESSAGE_BOOKING_PERIOD_CONSTRAINTS); + } + return new BookingPeriod(trimmedStartDate, trimmedEndDate); + } + + /** + * Parses a {@code date} into a {@code LocalDate} + * Leading and trailing whitespaces will be trimmed. + * @throws ParseException if {@code date} is invalid + */ + public static LocalDate parseDate(String date) throws ParseException { + requireNonNull(date); + String trimmedDate = date.trim(); + try { + return LocalDate.parse(trimmedDate, BookingPeriod.STRING_TO_DATE_FORMAT); + } catch (DateTimeException e) { + throw new ParseException(Messages.MESSAGE_INVALID_DATE); + } + } + + /** + * Parses a {@code date} into {@code String} + * @throws ParseException if {@code date} is invalid + */ + public static String parseDateToString(LocalDate date) { + requireNonNull(date); + return date.format(BookingPeriod.DATE_TO_STRING_FORMAT); + } + + /** + * Parses a cost string that may or may not be present into an {@code Optional} + * @param cost The cost that may or may not be present as an argument. + * @return A Money representation of the cost that may or may not be present. + * @throws ParseException if the cost is present but in the wrong format. + */ + public static Optional parseCost(Optional cost) throws ParseException { + requireNonNull(cost); + if (cost.isPresent() && !Money.isValidMoneyFormat(cost.get())) { + throw new ParseException(Money.MESSAGE_MONEY_CONSTRAINTS); + } + return cost.map(s -> new Money(s)); + } + + /** + * Parses a {@code String password} into a hashed password. + * Strips whitespace off the password, so passwords cannot begin or end + * with whitespaces. + */ + public static String parseAndHashPassword(String password) { + requireNonNull(password); + return PasswordHashUtil.hash(password.trim()); + } + + /** + * 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()); + } + + /** + * Returns true if at least one prefix contains {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + public static boolean areAnyPrefixPresent(ArgumentMultimap argumentMultimap, + Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if at least one prefix has an empty null value + * {@code ArgumentMultimap}. + */ + public static boolean areAnyPrefixValueNull(ArgumentMultimap argumentMultimap, + Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent() + && argumentMultimap.getValue(prefix).get().isEmpty()); + } } diff --git a/src/main/java/seedu/address/logic/parser/PasswordHashUtil.java b/src/main/java/seedu/address/logic/parser/PasswordHashUtil.java new file mode 100644 index 000000000000..88e4597bba61 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/PasswordHashUtil.java @@ -0,0 +1,51 @@ +package seedu.address.logic.parser; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * A utility class for hashing passwords. + * The code is taken from https://www.baeldung.com/sha-256-hashing-java. + */ +public class PasswordHashUtil { + + private static final String SHA_256_ALGORITHM = "SHA-256"; + + /** + * Uses the SHA-256 algorithm to hash a string. + * @param password The password supplied by the user + * @return The SHA-256 hash of the supplied password + */ + public static String hash(String password) { + try { + MessageDigest digest = MessageDigest.getInstance(SHA_256_ALGORITHM); + byte[] encodedHash = + digest.digest(password.getBytes(StandardCharsets.UTF_8)); + return bytesToHex(encodedHash); + } catch (NoSuchAlgorithmException e) { + // TODO: Handle this exception. By right, it is a default program by MessageDigest. + // We supply a valid String as algorithm. RuntimeExceptions not expected. + return ""; + } + } + + /** + * A helper function to convert a byte array to a hex string. + * @param hash The hashed password from the algorithm + * @return The string representation of the byte array + */ + private static String bytesToHex(byte[] hash) { + StringBuffer hexString = new StringBuffer(); + + for (int i = 0; i < hash.length; i++) { + String hex = Integer.toHexString(0xff & hash[i]); + if (hex.length() == 1) { + hexString.append('0'); + } + + hexString.append(hex); + } + return hexString.toString(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ReassignCommandParser.java b/src/main/java/seedu/address/logic/parser/ReassignCommandParser.java new file mode 100644 index 000000000000..537616a08205 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ReassignCommandParser.java @@ -0,0 +1,43 @@ +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_DATE_START; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NEW_ROOM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROOM; + +import java.time.LocalDate; + +import seedu.address.logic.commands.ReassignCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.room.RoomNumber; + +/** + * Parses input arguments and creates a new ReassignCommand object + */ +public class ReassignCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ReassignCommand + * and returns an ReassignCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ReassignCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_ROOM, PREFIX_DATE_START, PREFIX_NEW_ROOM); + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_ROOM, PREFIX_DATE_START, PREFIX_NEW_ROOM) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReassignCommand.MESSAGE_USAGE)); + } + try { + RoomNumber roomNumber = ParserUtil.parseRoomNumber(argMultimap.getValue(PREFIX_ROOM).get()); + LocalDate startDate = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE_START).get()); + RoomNumber newRoomNumber = ParserUtil.parseRoomNumber(argMultimap.getValue(PREFIX_NEW_ROOM).get()); + return new ReassignCommand(roomNumber, startDate, newRoomNumber); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReassignCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ServiceCommandParser.java b/src/main/java/seedu/address/logic/parser/ServiceCommandParser.java new file mode 100644 index 000000000000..dcb6322071df --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ServiceCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COST; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ITEM_NUMBER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROOM; + +import java.util.Optional; + +import seedu.address.logic.commands.ServiceCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.expenses.Money; +import seedu.address.model.room.RoomNumber; + +/** + * Parses input arguments and creates a new ServiceCommand object. + */ +public class ServiceCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ServiceCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_ROOM, PREFIX_ITEM_NUMBER, PREFIX_COST); + if (!ParserUtil.arePrefixesPresent(argMultimap, PREFIX_ROOM, PREFIX_ITEM_NUMBER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ServiceCommand.MESSAGE_USAGE)); + } + RoomNumber roomNumber = ParserUtil.parseRoomNumber(argMultimap.getValue(PREFIX_ROOM).get()); + String itemNumber = argMultimap.getValue(PREFIX_ITEM_NUMBER).get(); + Optional itemCost = ParserUtil.parseCost(argMultimap.getValue(PREFIX_COST)); + return new ServiceCommand(roomNumber, itemNumber, itemCost); + } +} 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/Concierge.java b/src/main/java/seedu/address/model/Concierge.java new file mode 100644 index 000000000000..d284e439cd38 --- /dev/null +++ b/src/main/java/seedu/address/model/Concierge.java @@ -0,0 +1,363 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import javafx.collections.ObservableList; +import seedu.address.model.expenses.Expense; +import seedu.address.model.expenses.ExpenseType; +import seedu.address.model.guest.Guest; +import seedu.address.model.guest.UniqueGuestList; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.UniqueRoomList; +import seedu.address.model.room.booking.Booking; +import seedu.address.model.room.booking.exceptions.ExpiredBookingException; +import seedu.address.model.room.booking.exceptions.NewBookingStartsBeforeOldBookingCheckedIn; +import seedu.address.model.room.booking.exceptions.OldBookingStartsBeforeNewBookingCheckedIn; +import seedu.address.model.room.booking.exceptions.OverlappingBookingException; +import seedu.address.model.room.exceptions.OriginalRoomReassignException; +import seedu.address.model.tag.Tag; + +/** + * Wraps all data at the Concierge level + * Duplicates are not allowed (by .isSameGuest comparison) + */ +public class Concierge implements ReadOnlyConcierge { + + private final UniqueGuestList guests; + private final UniqueGuestList checkedInGuests; + private final UniqueRoomList rooms; + private final Menu menu; + /* + * 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. + */ + { + guests = new UniqueGuestList(); + checkedInGuests = new UniqueGuestList(); + rooms = new UniqueRoomList(); + menu = new Menu(); + } + + public Concierge() {} + + /** + * Creates an Concierge using the data in the {@code toBeCopied} + */ + public Concierge(ReadOnlyConcierge toBeCopied) { + this(); + resetData(toBeCopied); + } + + //=========== Getters ============================================================= + + @Override + public ObservableList getGuestList() { + return guests.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getCheckedInGuestList() { + return checkedInGuests.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getRoomList() { + return rooms.asUnmodifiableObservableList(); + } + + @Override + public Menu getMenu() { + return menu; + } + + @Override + public Map getMenuMap() { + return menu.asUnmodifiableMap(); + } + + //=========== Guest operations ============================================================= + + /** + * Replaces the contents of the guest list with {@code guests}. + * {@code guests} must not contain duplicate guests. + */ + public void setGuests(List guests) { + this.guests.setGuests(guests); + } + + /** + * Replaces the contents of the checked-in guest list with {@code guests}. + * {@code guests} must not contain duplicate guests. + */ + public void setCheckedInGuests(List guests) { + this.checkedInGuests.setGuests(guests); + } + + /** + * Adds a guest to Concierge's guest list. + * The guest must not already exist in Concierge's guest list. + */ + public void addGuest(Guest g) { + guests.add(g); + } + + /** + * Adds a guest to Concierge's checked-in guest list. + * If the guest already exists in the checked-in guest list, do nothing. This is expected + * behavior because a guest can make multiple bookings over overlapping booking periods (just for different + * rooms). + */ + public void addCheckedInGuestIfNotPresent(Guest g) { + if (hasCheckedInGuest(g)) { + return; + } + checkedInGuests.add(g); + } + + /** + * Removes {@code key} from this {@code Concierge}'s guest list. + * {@code key} must exist in Concierge's guest list. + */ + public void removeGuest(Guest key) { + guests.remove(key); + } + + /** + * Removes {@code key} from this {@code Concierge}'s checked-in guest list. + * {@code key} must exist in Concierge's checked-in guest list. + */ + public void removeCheckedInGuest(Guest key) { + checkedInGuests.remove(key); + } + + //=========== Room operations ============================================================= + + /** + * Adds given tags to the specified room + */ + public void addRoomTags(RoomNumber roomNumber, Tag... tags) { + Room room = rooms.getRoom(roomNumber); + Room editedRoom = room.addTags(tags); + rooms.setRoom(room, editedRoom); + } + + /** + * Replaces the contents of the room list with {@code rooms}. + * {@code rooms} must not contain duplicate rooms. + */ + public void setRooms(List rooms) { + this.rooms.setRooms(rooms); + } + + /** + * Reassigns the booking identified by {@code startDate} in the room identified by {@code roomNumber} to the room + * identified by {@code newRoomNumber}. The expenses are moved over to the new room as well.
+ * The following checks are conducted in succession, each possibly throwing an exception:
+ * If room and new room are equal, throw OriginalRoomReassignException.
+ * If the booking does not exist, throw NoBookingFoundException.
+ * If the booking is expired, or the new room's first booking is expired, throw ExpiredBookingException.
+ * If the booking starts before the new booking, and the new booking is checked-in, + * throw OldBookingStartsBeforeNewBookingCheckedIn.
+ * If the new booking starts before the old booking, and the old booking is checked-in, + * throw NewBookingStartsBeforeOldBookingCheckedIn.
+ * If the booking overlaps with any of new room's bookings, throw OverlappingBookingException. + */ + public void reassignRoom(RoomNumber roomNumber, LocalDate startDate, RoomNumber newRoomNumber) { + Room room = rooms.getRoom(roomNumber); + Room newRoom = rooms.getRoom(newRoomNumber); + + if (room.equals(newRoom)) { + throw new OriginalRoomReassignException(); + } + + Booking bookingToReassign = room.getBookings() + .getFirstBookingByPredicate(booking -> booking.getBookingPeriod().getStartDate().equals(startDate)); + if (bookingToReassign.isExpired()) { + throw new ExpiredBookingException(); + } + + if (newRoom.hasBookings()) { + Booking newRoomFirstBooking = newRoom.getBookings().getFirstBooking(); + + if (newRoomFirstBooking.isExpired()) { + throw new ExpiredBookingException(); + } + + if (bookingToReassign.isOverlapping(newRoomFirstBooking)) { + throw new OverlappingBookingException(); + } + + if (bookingToReassign.startsBefore(newRoomFirstBooking) && newRoomFirstBooking.getIsCheckedIn()) { + throw new OldBookingStartsBeforeNewBookingCheckedIn(); + } + + if (newRoomFirstBooking.startsBefore(bookingToReassign) && bookingToReassign.getIsCheckedIn()) { + throw new NewBookingStartsBeforeOldBookingCheckedIn(); + } + } + + Room editedNewRoom = newRoom.addBooking(bookingToReassign); + for (Expense expense : room.getExpenses().getExpensesList()) { + editedNewRoom = editedNewRoom.addExpense(expense); + } + rooms.setRoom(newRoom, editedNewRoom); + + Room editedRoom = room.checkout(bookingToReassign); + rooms.setRoom(room, editedRoom); + } + + /** + * Add a booking to a room identified by its room number. + */ + public void addBooking(RoomNumber roomNumber, Booking booking) { + Room room = rooms.getRoom(roomNumber); + Room editedRoom = room.addBooking(booking); + rooms.setRoom(room, editedRoom); + } + + /** + * Checks in the room using its room number and adds guest to the checked-in guest list + */ + public void checkInRoom(RoomNumber roomNumber) { + Room room = rooms.getRoom(roomNumber); + Room checkedInRoom = room.checkIn(); + rooms.setRoom(room, checkedInRoom); + + // First booking is guaranteed to be present after executing room.checkIn() above + Guest guestToCheckIn = checkedInRoom.getBookings().getFirstBooking().getGuest(); + + addCheckedInGuestIfNotPresent(guestToCheckIn); + } + + /** + * Checks out a room's first booking + */ + public void checkoutRoom(RoomNumber roomNumber) { + Room room = rooms.getRoom(roomNumber); + checkoutRoom(room, room.getBookings().getFirstBooking()); + } + + /** + * Checks out a room's booking using the specified start date + */ + public void checkoutRoom(RoomNumber roomNumber, LocalDate startDate) { + Room room = rooms.getRoom(roomNumber); + checkoutRoom(room, room + .getBookings() + .getFirstBookingByPredicate(booking -> booking.getBookingPeriod().getStartDate().equals(startDate))); + } + + /** + * Checks out the given booking from the given room. + * + * If the booking's guest was checked-in (i.e. exists in the checked-in guest list), check: + * 1) If the guest does not have checked-in bookings in other rooms, remove him from the checked-in guest list. + * 2) If the guest does not exists in the archived guest list. add him to the archived guest list + * + * Reason for initial check: Guests who did not check-in do not count as having stayed in the hotel before. + * Reason for 1): Guests can have multiple checked-in bookings at once. + * Reason for 2): Guests may have stayed in the hotel before, and would thus already be in the archived guest list. + */ + private void checkoutRoom(Room room, Booking bookingToCheckout) { + rooms.setRoom(room, room.checkout(bookingToCheckout)); + + Guest guestToCheckout = bookingToCheckout.getGuest(); + + if (hasCheckedInGuest(guestToCheckout)) { + boolean guestHasOtherBookings = rooms.asUnmodifiableObservableList() + .stream().anyMatch(r -> r.getBookings().getSortedBookingsSet() + .stream().anyMatch(b -> b.getIsCheckedIn() && b.getGuest().equals(guestToCheckout))); + if (!guestHasOtherBookings) { + removeCheckedInGuest(guestToCheckout); + } + + if (!hasGuest(guestToCheckout)) { + addGuest(guestToCheckout); + } + } + + } + + public void setMenu(Map menu) { + this.menu.setMenu(menu); + } + + /** + * Adds an expense to the room using its room number + */ + public void addExpense(RoomNumber roomNumber, Expense expense) { + Room room = rooms.getRoom(roomNumber); + Room editedRoom = room.addExpense(expense); + rooms.setRoom(room, editedRoom); + } + + //=========== Reset data ============================================================= + + /** + * Resets the existing data of this {@code Concierge} with {@code newData}. + */ + public void resetData(ReadOnlyConcierge newData) { + requireNonNull(newData); + + setGuests(newData.getGuestList()); + setCheckedInGuests(newData.getCheckedInGuestList()); + setRooms(newData.getRoomList()); + setMenu(newData.getMenuMap()); + } + + //=========== Boolean checkers ============================================================= + + /** + * Returns true if a guest with the same identity as {@code guest} exists in Concierge. + */ + public boolean hasGuest(Guest guest) { + requireNonNull(guest); + return guests.contains(guest); + } + + /** + * Returns true if a guest with the same identity as {@code guest} exists in Concierge's checked-in guest list. + */ + public boolean hasCheckedInGuest(Guest guest) { + requireNonNull(guest); + return checkedInGuests.contains(guest); + } + + //// util methods + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Guests:\n") + .append(guests.asUnmodifiableObservableList()) + .append("\nChecked-in guests:\n") + .append(checkedInGuests.asUnmodifiableObservableList()) + .append("\nRooms:\n") + .append(rooms.asUnmodifiableObservableList()); + return sb.toString(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Concierge // instanceof handles nulls + && guests.equals(((Concierge) other).guests) + && checkedInGuests.equals(((Concierge) other).checkedInGuests) + && rooms.equals(((Concierge) other).rooms)); + } + + @Override + public int hashCode() { + return Objects.hash(guests, checkedInGuests, rooms); + } + +} diff --git a/src/main/java/seedu/address/model/Menu.java b/src/main/java/seedu/address/model/Menu.java new file mode 100644 index 000000000000..825bb64d80e3 --- /dev/null +++ b/src/main/java/seedu/address/model/Menu.java @@ -0,0 +1,67 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import seedu.address.model.expenses.ExpenseType; +import seedu.address.model.expenses.exceptions.ItemNotFoundException; + +/** + * A representation of the menu of the items and services for sale. + * This class should be read-only. + */ +public class Menu { + + private HashMap numberToType; + + public Menu() { + numberToType = new HashMap<>(); + } + + public Menu(Menu other) { + requireNonNull(other); + numberToType = new HashMap<>(other.numberToType); + } + + public void setMenu(Map other) { + requireNonNull(other); + numberToType.putAll(other); + } + + public Map asUnmodifiableMap() { + return Collections.unmodifiableMap(numberToType); + } + + /** + * Checks that the given item number exists in the menu. + * @param item The item number to check. + * @return True if the item number is in the menu, false otherwise. + */ + public boolean isValidMenuNumber(String item) { + requireNonNull(item); + return numberToType.containsKey(item); + } + + public ExpenseType getExpenseType(String item) { + requireNonNull(item); + if (!isValidMenuNumber(item)) { + throw new ItemNotFoundException(); + } + return numberToType.get(item); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Menu // instanceof handles nulls + && numberToType.equals(((Menu) other).numberToType)); // state check + } + + @Override + public int hashCode() { + return numberToType.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index ac4521f33199..cb036ca19b7f 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,78 +1,194 @@ package seedu.address.model; +import java.time.LocalDate; +import java.util.Optional; import java.util.function.Predicate; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.logic.parser.Prefix; +import seedu.address.model.expenses.Expense; +import seedu.address.model.guest.Guest; +import seedu.address.model.login.InvalidLogInException; +import seedu.address.model.login.InvalidLogOutException; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.booking.Booking; +import seedu.address.model.tag.Tag; /** * 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/false */ + Predicate PREDICATE_SHOW_ALL_GUESTS = unused -> true; + Predicate PREDICATE_SHOW_NO_GUESTS = unused -> false; + Predicate PREDICATE_SHOW_ALL_ROOMS = unused -> true; + Predicate PREDICATE_SHOW_NO_ROOMS = unused -> false; + /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); + void resetData(ReadOnlyConcierge newData); + + /** Returns the Concierge */ + ReadOnlyConcierge getConcierge(); + + /** Returns the Menu.*/ + Menu getMenu(); + + // =========== Signing in. ================================================ + + /** + * Returns true if Concierge is currently logged in. + */ + boolean isSignedIn(); + + /** + * Returns an Optional containing the username currently tagged to the + * session. + */ + Optional getUsername(); + + /** + * Attempts to sign in with username {@code username} and password {@code + * hashedPassword}. + */ + void signIn(String username, String hashedPassword) throws InvalidLogInException; + + /** + * Attempts to sign out of Concierge. + */ + void signOut() throws InvalidLogOutException; + + // =========== Methods for guest. ========================================= - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + /** + * Returns true if a guest with the same identity as {@code guest} exists in Concierge. + */ + boolean hasGuest(Guest guest); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Deletes the given guest. + * The guest must exist in Concierge. */ - boolean hasPerson(Person person); + void deleteGuest(Guest target); /** - * Deletes the given person. - * The person must exist in the address book. + * Adds the given guest. + * {@code guest} must not already exist in Concierge. */ - void deletePerson(Person target); + void addGuest(Guest guest); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Get the flag representing the last displayed list. */ - void addPerson(Person person); + Prefix getDisplayedListFlag(); /** - * Replaces the given person {@code target} 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. + * Set the flag representing the last displayed list. */ - void updatePerson(Person target, Person editedPerson); + void setDisplayedListFlag(Prefix flag); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered guest list */ + ObservableList getFilteredGuestList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the filter of the filtered guest list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredGuestList(Predicate predicate); + + /** + * Returns an unmodifiable view of the list of checked-in {@code Guest} backed by the internal list of + * {@code versionedConcierge} + */ + ObservableList getFilteredCheckedInGuestList(); + + void updateFilteredCheckedInGuestList(Predicate predicate); + + /** Returns an unmodifiable view of the filtered room list */ + ObservableList getFilteredRoomList(); + + /** + * Updates the filter of the filtered room list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredRoomList(Predicate predicate); + + //=========== Methods for room. =========================================== + + /** + * Adds given tags to the specified room + */ + public void addRoomTags(RoomNumber roomNumber, Tag... tags); + + + /** + * Reassigns the booking identified by {@code startDate} in the room identified by {@code roomNumber} to the room + * identified by {@code newRoomNumber} + */ + public void reassignRoom(RoomNumber roomNumber, LocalDate startDate, RoomNumber newRoomNumber); + + /** + * Add a booking to a room identified by its room number. + */ + void addBooking(RoomNumber roomNumber, Booking booking); + + /** + * Displays room list instead of guest list + */ + //void displayRoomList(Predicate predicate); + + /** + * Checks in the room by its room number + */ + void checkInRoom(RoomNumber roomNumber); + + /** + * Checks out the room. + * @param roomNumber + */ + void checkoutRoom(RoomNumber roomNumber); + + /** + * Checks out a room's booking using its room number and the specified start date + */ + void checkoutRoom(RoomNumber roomNumber, LocalDate startDate); + + /** + * Adds an Expense to a room. + */ + void addExpense(RoomNumber roomNumber, Expense expense); + + + // =========== Methods for undo and redo. ================================= + + /** + * Resets the undo/ redo history. + */ + void resetUndoRedoHistory(); /** - * Returns true if the model has previous address book states to restore. + * Returns true if the model has previous Concierge states to restore. */ - boolean canUndoAddressBook(); + boolean canUndoConcierge(); /** - * Returns true if the model has undone address book states to restore. + * Returns true if the model has undone Concierge states to restore. */ - boolean canRedoAddressBook(); + boolean canRedoConcierge(); /** - * Restores the model's address book to its previous state. + * Restores the model's Concierge to its previous state. */ - void undoAddressBook(); + void undoConcierge(); /** - * Restores the model's address book to its previously undone state. + * Restores the model's Concierge to its previously undone state. */ - void redoAddressBook(); + void redoConcierge(); /** - * Saves the current address book state for undo/redo. + * Saves the current Concierge state for undo/redo. */ - void commitAddressBook(); + void commitConcierge(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index a664602ef5b1..32bd190d071d 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -3,6 +3,8 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.time.LocalDate; +import java.util.Optional; import java.util.function.Predicate; import java.util.logging.Logger; @@ -11,122 +13,264 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.ComponentManager; 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.ConciergeChangedEvent; +import seedu.address.logic.parser.CliSyntax; +import seedu.address.logic.parser.Prefix; +import seedu.address.model.expenses.Expense; +import seedu.address.model.guest.Guest; +import seedu.address.model.login.InvalidLogInException; +import seedu.address.model.login.InvalidLogOutException; +import seedu.address.model.login.LogInManager; +import seedu.address.model.login.PasswordHashList; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.booking.Booking; +import seedu.address.model.tag.Tag; /** - * Represents the in-memory model of the address book data. + * Represents the in-memory model of Concierge data. */ 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 VersionedConcierge versionedConcierge; + private final FilteredList filteredGuests; + private final FilteredList filteredRooms; + private final FilteredList filteredCheckedInGuests; + private final LogInManager logInManager; + private Prefix displayedListFlag; /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * Initializes a ModelManager with the given concierge and userPrefs. + * This method remains to support existing tests which do not require the + * LogInHelper module. */ - public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { + public ModelManager(ReadOnlyConcierge concierge, UserPrefs userPrefs) { + this(concierge, userPrefs, new PasswordHashList()); + } + + /** + * Initializes a ModelManager with the given {@code concierge}, + * {@code userPrefs} and {@code passwordRef}. + */ + public ModelManager(ReadOnlyConcierge concierge, UserPrefs userPrefs, + PasswordHashList passwordRef) { super(); - requireAllNonNull(addressBook, userPrefs); + requireAllNonNull(concierge, userPrefs); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + logger.fine("Initializing with Concierge: " + concierge + + " and user prefs " + userPrefs + + " and password list " + passwordRef); - versionedAddressBook = new VersionedAddressBook(addressBook); - filteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); + versionedConcierge = new VersionedConcierge(concierge); + filteredGuests = new FilteredList<>(versionedConcierge.getGuestList()); + filteredRooms = new FilteredList<>(versionedConcierge.getRoomList()); + filteredCheckedInGuests = new FilteredList<>(versionedConcierge.getCheckedInGuestList()); + logInManager = new LogInManager(passwordRef); + displayedListFlag = CliSyntax.FLAG_ROOM; } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new Concierge(), new UserPrefs()); } @Override - public void resetData(ReadOnlyAddressBook newData) { - versionedAddressBook.resetData(newData); - indicateAddressBookChanged(); + public void resetData(ReadOnlyConcierge newData) { + versionedConcierge.resetData(newData); + indicateConciergeChanged(); } @Override - public ReadOnlyAddressBook getAddressBook() { - return versionedAddressBook; + public ReadOnlyConcierge getConcierge() { + return versionedConcierge; + } + + @Override + public Menu getMenu() { + return versionedConcierge.getMenu(); } /** Raises an event to indicate the model has changed */ - private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(versionedAddressBook)); + private void indicateConciergeChanged() { + raise(new ConciergeChangedEvent(versionedConcierge)); + } + + @Override + public boolean isSignedIn() { + return logInManager.isSignedIn(); } @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return versionedAddressBook.hasPerson(person); + public Optional getUsername() { + return logInManager.getUsername(); } @Override - public void deletePerson(Person target) { - versionedAddressBook.removePerson(target); - indicateAddressBookChanged(); + public void signIn(String userName, String hashedPassword) throws InvalidLogInException { + logInManager.signIn(userName, hashedPassword); } @Override - public void addPerson(Person person) { - versionedAddressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - indicateAddressBookChanged(); + public void signOut() throws InvalidLogOutException { + logInManager.signOut(); } @Override - public void updatePerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public boolean hasGuest(Guest guest) { + requireNonNull(guest); + return versionedConcierge.hasGuest(guest); + } - versionedAddressBook.updatePerson(target, editedPerson); - indicateAddressBookChanged(); + @Override + public void deleteGuest(Guest target) { + versionedConcierge.removeGuest(target); + indicateConciergeChanged(); } - //=========== Filtered Person List Accessors ============================================================= + @Override + public void addGuest(Guest guest) { + versionedConcierge.addGuest(guest); + indicateConciergeChanged(); + } + + //=========== Displayed List Getter and Setter ============================================================= + + @Override + public Prefix getDisplayedListFlag() { + return displayedListFlag; + } + + @Override + public void setDisplayedListFlag(Prefix flag) { + displayedListFlag = flag; + } + + //=========== Filtered Guest 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 Guest} backed by the internal list of + * {@code versionedConcierge} */ @Override - public ObservableList getFilteredPersonList() { - return FXCollections.unmodifiableObservableList(filteredPersons); + public ObservableList getFilteredGuestList() { + return FXCollections.unmodifiableObservableList(filteredGuests); } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredGuestList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredGuests.setPredicate(predicate); } - //=========== Undo/Redo ================================================================================= + /** + * Returns an unmodifiable view of the list of checked-in {@code Guest} backed by the internal list of + * {@code versionedConcierge} + */ + @Override + public ObservableList getFilteredCheckedInGuestList() { + return FXCollections.unmodifiableObservableList(filteredCheckedInGuests); + } @Override - public boolean canUndoAddressBook() { - return versionedAddressBook.canUndo(); + public void updateFilteredCheckedInGuestList(Predicate predicate) { + requireNonNull(predicate); + filteredCheckedInGuests.setPredicate(predicate); + } + + //=========== Filtered Room List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Room} backed by the internal list of + * {@code versionedConcierge} + */ + @Override + public ObservableList getFilteredRoomList() { + return FXCollections.unmodifiableObservableList(filteredRooms); } @Override - public boolean canRedoAddressBook() { - return versionedAddressBook.canRedo(); + public void updateFilteredRoomList(Predicate predicate) { + requireNonNull(predicate); + filteredRooms.setPredicate(predicate); } + //=========== Room ======================================================= + @Override - public void undoAddressBook() { - versionedAddressBook.undo(); - indicateAddressBookChanged(); + public void addRoomTags(RoomNumber roomNumber, Tag... tags) { + versionedConcierge.addRoomTags(roomNumber, tags); + indicateConciergeChanged(); } @Override - public void redoAddressBook() { - versionedAddressBook.redo(); - indicateAddressBookChanged(); + public void reassignRoom(RoomNumber roomNumber, LocalDate startDate, RoomNumber newRoomNumber) { + versionedConcierge.reassignRoom(roomNumber, startDate, newRoomNumber); + indicateConciergeChanged(); } @Override - public void commitAddressBook() { - versionedAddressBook.commit(); + public void addBooking(RoomNumber roomNumber, Booking booking) { + versionedConcierge.addBooking(roomNumber, booking); + indicateConciergeChanged(); + } + + @Override + public void checkInRoom(RoomNumber roomNumber) { + versionedConcierge.checkInRoom(roomNumber); + indicateConciergeChanged(); + } + + @Override + public void checkoutRoom(RoomNumber roomNumber) { + versionedConcierge.checkoutRoom(roomNumber); + indicateConciergeChanged(); + } + + @Override + public void checkoutRoom(RoomNumber roomNumber, LocalDate startDate) { + versionedConcierge.checkoutRoom(roomNumber, startDate); + indicateConciergeChanged(); + } + + // ========= Expenses ================================================= + + @Override + public void addExpense(RoomNumber roomNumber, Expense expense) { + versionedConcierge.addExpense(roomNumber, expense); + indicateConciergeChanged(); + } + + //=========== Undo/Redo ================================================ + + @Override + public void resetUndoRedoHistory() { + versionedConcierge.resetUndoRedoHistory(); } + + @Override + public boolean canUndoConcierge() { + return versionedConcierge.canUndo(); + } + + @Override + public boolean canRedoConcierge() { + return versionedConcierge.canRedo(); + } + + @Override + public void undoConcierge() { + versionedConcierge.undo(); + indicateConciergeChanged(); + } + + @Override + public void redoConcierge() { + versionedConcierge.redo(); + indicateConciergeChanged(); + } + + @Override + public void commitConcierge() { + versionedConcierge.commit(); } @Override @@ -143,8 +287,20 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; - return versionedAddressBook.equals(other.versionedAddressBook) - && filteredPersons.equals(other.filteredPersons); + return versionedConcierge.equals(other.versionedConcierge) + && filteredGuests.equals(other.filteredGuests) + && filteredCheckedInGuests.equals(other.filteredCheckedInGuests) + && filteredRooms.equals(other.filteredRooms) + && displayedListFlag.equals(other.displayedListFlag); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("displayed list flag: ") + .append(displayedListFlag) + .append("\n"); + return sb.toString(); } } 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/ReadOnlyConcierge.java b/src/main/java/seedu/address/model/ReadOnlyConcierge.java new file mode 100644 index 000000000000..399ef27c9c1b --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyConcierge.java @@ -0,0 +1,42 @@ +package seedu.address.model; + +import java.util.Map; + +import javafx.collections.ObservableList; +import seedu.address.model.expenses.ExpenseType; +import seedu.address.model.guest.Guest; +import seedu.address.model.room.Room; + +/** + * Unmodifiable view of an Concierge + */ +public interface ReadOnlyConcierge { + + /** + * Returns an unmodifiable view of the guest list. + * This list will not contain any duplicate guests. + */ + ObservableList getGuestList(); + + /** + * Returns an unmodifiable view of the checked-in guest list. + * This list will not contain any duplicate guests. + */ + ObservableList getCheckedInGuestList(); + + /** + * Returns an unmodifiable view of the room list. + * This list will not contain any duplicate rooms. + */ + ObservableList getRoomList(); + + /** + * Returns the menu for reference purposes. + */ + Menu getMenu(); + + /** + * Returns an unmodifiable view of the menu. + */ + Map getMenuMap(); +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 980b2b388852..6e617eb42016 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -12,7 +12,8 @@ public class UserPrefs { private GuiSettings guiSettings; - private Path addressBookFilePath = Paths.get("data" , "addressbook.xml"); + private Path conciergeFilePath = Paths.get("data" , "concierge.xml"); + private Path passwordsFilePath = Paths.get("passwords.json"); public UserPrefs() { setGuiSettings(500, 500, 0, 0); @@ -30,12 +31,20 @@ 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 getConciergeFilePath() { + return conciergeFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - this.addressBookFilePath = addressBookFilePath; + public void setConciergeFilePath(Path conciergeFilePath) { + this.conciergeFilePath = conciergeFilePath; + } + + public Path getPasswordsFilePath() { + return passwordsFilePath; + } + + public void setPasswordsFilePath(Path passwordsFilePath) { + this.passwordsFilePath = passwordsFilePath; } @Override @@ -50,19 +59,19 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return Objects.equals(guiSettings, o.guiSettings) - && Objects.equals(addressBookFilePath, o.addressBookFilePath); + && Objects.equals(conciergeFilePath, o.conciergeFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, conciergeFilePath); } @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 : " + conciergeFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedAddressBook.java deleted file mode 100644 index 227a335045d7..000000000000 --- a/src/main/java/seedu/address/model/VersionedAddressBook.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.model; - -import java.util.ArrayList; -import java.util.List; - -/** - * {@code AddressBook} that keeps track of its own history. - */ -public class VersionedAddressBook extends AddressBook { - - private final List addressBookStateList; - private int currentStatePointer; - - public VersionedAddressBook(ReadOnlyAddressBook initialState) { - super(initialState); - - addressBookStateList = new ArrayList<>(); - addressBookStateList.add(new AddressBook(initialState)); - currentStatePointer = 0; - } - - /** - * Saves a copy of the current {@code AddressBook} 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)); - currentStatePointer++; - } - - private void removeStatesAfterCurrentPointer() { - addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear(); - } - - /** - * Restores the address book to its previous state. - */ - public void undo() { - if (!canUndo()) { - throw new NoUndoableStateException(); - } - currentStatePointer--; - resetData(addressBookStateList.get(currentStatePointer)); - } - - /** - * Restores the address book to its previously undone state. - */ - public void redo() { - if (!canRedo()) { - throw new NoRedoableStateException(); - } - currentStatePointer++; - resetData(addressBookStateList.get(currentStatePointer)); - } - - /** - * Returns true if {@code undo()} has address book states to undo. - */ - public boolean canUndo() { - return currentStatePointer > 0; - } - - /** - * Returns true if {@code redo()} has address book states to redo. - */ - public boolean canRedo() { - return currentStatePointer < addressBookStateList.size() - 1; - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof VersionedAddressBook)) { - return false; - } - - VersionedAddressBook otherVersionedAddressBook = (VersionedAddressBook) other; - - // state check - return super.equals(otherVersionedAddressBook) - && addressBookStateList.equals(otherVersionedAddressBook.addressBookStateList) - && currentStatePointer == otherVersionedAddressBook.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 addressBookState 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/VersionedConcierge.java b/src/main/java/seedu/address/model/VersionedConcierge.java new file mode 100644 index 000000000000..d97786d9148a --- /dev/null +++ b/src/main/java/seedu/address/model/VersionedConcierge.java @@ -0,0 +1,119 @@ +package seedu.address.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@code Concierge} that keeps track of its own history. + */ +public class VersionedConcierge extends Concierge { + + private final List conciergeStateList; + private int currentStatePointer; + + public VersionedConcierge(ReadOnlyConcierge initialState) { + super(initialState); + + conciergeStateList = new ArrayList<>(); + conciergeStateList.add(new Concierge(initialState)); + currentStatePointer = 0; + } + + /** + * Saves a copy of the current {@code Concierge} state at the end of the state list. + * Undone states are removed from the state list. + */ + public void commit() { + removeStatesAfterCurrentPointer(); + conciergeStateList.add(new Concierge(this)); + currentStatePointer++; + } + + private void removeStatesAfterCurrentPointer() { + conciergeStateList.subList(currentStatePointer + 1, conciergeStateList.size()).clear(); + } + + /** + * Restores Concierge to its previous state. + */ + public void undo() { + if (!canUndo()) { + throw new NoUndoableStateException(); + } + currentStatePointer--; + resetData(conciergeStateList.get(currentStatePointer)); + } + + /** + * Restores Concierge to its previously undone state. + */ + public void redo() { + if (!canRedo()) { + throw new NoRedoableStateException(); + } + currentStatePointer++; + resetData(conciergeStateList.get(currentStatePointer)); + } + + /** + * Returns true if {@code undo()} has Concierge states to undo. + */ + public boolean canUndo() { + return currentStatePointer > 0; + } + + /** + * Returns true if {@code redo()} has Concierge states to redo. + */ + public boolean canRedo() { + return currentStatePointer < conciergeStateList.size() - 1; + } + + /** + * Resets the undo and redo history. {@code undo()} and {@code redo()} + * will throw the respective {@code NoUndoableStateException} and {@code + * NoRedoableStateException}. + */ + public void resetUndoRedoHistory() { + currentStatePointer = -1; + commit(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof VersionedConcierge)) { + return false; + } + + VersionedConcierge otherVersionedConcierge = (VersionedConcierge) other; + + // state check + return super.equals(otherVersionedConcierge) + && conciergeStateList.equals(otherVersionedConcierge.conciergeStateList) + && currentStatePointer == otherVersionedConcierge.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 conciergeState 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 conciergeState list, unable to redo."); + } + } +} diff --git a/src/main/java/seedu/address/model/expenses/Expense.java b/src/main/java/seedu/address/model/expenses/Expense.java new file mode 100644 index 000000000000..2e68ad48a9d2 --- /dev/null +++ b/src/main/java/seedu/address/model/expenses/Expense.java @@ -0,0 +1,122 @@ +package seedu.address.model.expenses; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import seedu.address.model.expenses.exceptions.ItemNotFoundException; + +/** + * Represents a single spending on a single type of service. + */ +public class Expense { + + /** + * Standard date-time format used for this hotel's expenses + */ + public static final DateTimeFormatter DATETIME_FORMAT = DateTimeFormatter.ofPattern("d/M/u HH:mm:ss"); + + private final Money cost; + private final ExpenseType type; + private final LocalDateTime dateTime; + + /** + * Constructs an {@code Expense} object. + * + * @param type The menu number of the product or service exchanged for with this expense. + * @param cost The monetary value of the expense. + */ + public Expense(ExpenseType type, Money cost) { + requireNonNull(type); + this.type = type; + this.cost = cost; + this.dateTime = LocalDateTime.now(); + } + + /** + * Constructs an {@code Expense} object. + * Used if the user is charging the base price and hence does not need to manually enter a price. + * + * @param type The product or service exchanged for with this expense. + * @throws ItemNotFoundException if the item's menu number does not exist in the menu. + */ + public Expense(ExpenseType type) { + requireNonNull(type); + this.type = type; + this.cost = this.type.getItemCost(); + this.dateTime = LocalDateTime.now(); + } + + public Expense(ExpenseType type, Money cost, LocalDateTime dateTime) { + requireAllNonNull(type, dateTime); + this.type = type; + this.cost = cost; + this.dateTime = dateTime; + } + + /** + * Provides the cost of this expense. + * + * @return The monetary value of the expense. + */ + public Money getCost() { + return cost; + } + + /** + * Provides the expense type of the item purchased in this expense. + * + * @return The ExpenseType object of the product or service exchanged for with this expense. + */ + public ExpenseType getExpenseType() { + return type; + } + + /** + * Provides the name of the item purchased in this expense. + * + * @return The name of the product or service exchanged for with this expense. + */ + public String getItemName() { + return type.getItemName(); + } + + /** + * Provides the date of the transaction. + * + * @return The date and time of this transaction. + */ + public LocalDateTime getDateTime() { + return dateTime; + } + + /** + * Provides the date of the transaction in a string. + * + * @return The date and time of this transaction in string. + */ + public String getDateTimeString() { + return dateTime.format(DATETIME_FORMAT); + } + + @Override + public String toString() { + return cost + " spent on " + type.getItemName() + " on " + getDateTimeString(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Expenses // instanceof handles nulls + && cost.equals(((Expense) other).cost) + && type.equals(((Expense) other).type) + && dateTime.equals(((Expense) other).dateTime)); // state check + } + + @Override + public int hashCode() { + return dateTime.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/expenses/ExpenseType.java b/src/main/java/seedu/address/model/expenses/ExpenseType.java new file mode 100644 index 000000000000..606ca2c684bc --- /dev/null +++ b/src/main/java/seedu/address/model/expenses/ExpenseType.java @@ -0,0 +1,74 @@ +package seedu.address.model.expenses; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +/** + * Contains all the different types of expenses available at the hotel. + * All products and services have a unique menu number. + * ExpenseType objects will only be created when the menu is read from the hard disk. + */ +public class ExpenseType { + + public static final String MESSAGE_NUMBER_EMPTY = "The given number is empty."; + public static final String MESSAGE_NAME_EMPTY = "The given name is empty."; + + private final String itemNumber; + private final String itemName; + private final Money itemCost; + + /** + * Constructor for an {@code ExpenseType} object. + * @param itemNumber The menu number of the item. + * @param itemName The name of the item. + * @param itemCost The cost of the item. + */ + public ExpenseType(String itemNumber, String itemName, Money itemCost) { + requireAllNonNull(itemNumber, itemName); + if (itemNumber.equals("")) { + throw new IllegalArgumentException(MESSAGE_NUMBER_EMPTY); + } + if (itemName.equals("")) { + throw new IllegalArgumentException(MESSAGE_NAME_EMPTY); + } + this.itemNumber = itemNumber; + this.itemName = itemName; + this.itemCost = itemCost; + } + + public ExpenseType(ExpenseType expenseTypeToCopy) { + this.itemNumber = expenseTypeToCopy.getItemNumber(); + this.itemName = expenseTypeToCopy.getItemName(); + this.itemCost = expenseTypeToCopy.getItemCost(); + } + + public String getItemNumber() { + return itemNumber; + } + + public String getItemName() { + return itemName; + } + + public Money getItemCost() { + return itemCost; + } + + @Override + public String toString() { + return itemNumber + " " + itemName + " $" + itemCost; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ExpenseType // instanceof handles nulls + && itemNumber.equals(((ExpenseType) other).itemNumber) + && itemName.equals(((ExpenseType) other).itemName) + && itemCost.equals(((ExpenseType) other).itemCost)); // state check + } + + @Override + public int hashCode() { + return itemNumber.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/expenses/Expenses.java b/src/main/java/seedu/address/model/expenses/Expenses.java new file mode 100644 index 000000000000..39d0331b7566 --- /dev/null +++ b/src/main/java/seedu/address/model/expenses/Expenses.java @@ -0,0 +1,109 @@ +package seedu.address.model.expenses; + +import static java.util.Objects.requireNonNull; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Contains all of the expenses incurred by the guests of a given room. + */ +public class Expenses { + + private final List expenseList; + + /** + * Constructs an {@code Expenses} object. + */ + public Expenses() { + expenseList = new LinkedList<>(); + } + + public Expenses(List expenseList) { + requireNonNull(expenseList); + this.expenseList = new LinkedList<>(expenseList); + } + + public List getExpensesList() { + return Collections.unmodifiableList(expenseList); + } + + /** + * Adds a new {@code Expense} to the current expenses. + * New expense is added in front as it is more likely that recent expenses are accessed. + * + * @param newExpense The new expense incurred. + * @return A new Expenses object with the new expense. + */ + public Expenses addExpense(Expense newExpense) { + requireNonNull(newExpense); + List newList = new LinkedList<>(expenseList); + newList.add(0, newExpense); + return new Expenses(newList); + } + + /** + * Clears all of the expense records when called. + * Usually called when guests have checked out. + * + * @return An Expenses object with no expenses. + */ + public Expenses clearExpenses() { + return new Expenses(); + } + + /** + * Get the total cost of all the expenses as a string. + */ + public String toStringTotalCost() { + BigDecimal totalCost = new BigDecimal(0); + for (Expense e : expenseList) { + totalCost = totalCost.add(new BigDecimal(e.getCost().toString())); + totalCost = totalCost.setScale(2, RoundingMode.HALF_EVEN); + } + return totalCost.toString(); + } + + /** + * Returns a table-formatted string of all expenses to be displayed on the detailed panel. + */ + public String toStringSummary() { + String format = "%1$-20s %2$-7s %3$-30s %4$13s\n"; // datetime number name cost + StringBuilder output = new StringBuilder(); + for (Expense expense : expenseList) { + output.append(String.format(format, + expense.getDateTimeString(), + expense.getExpenseType().getItemNumber(), + expense.getExpenseType().getItemName(), + expense.getCost())); + } + // anyone who has a nicer way of writing the Total line, do edit + String spaces = String.join("", Collections.nCopies(29, " ")); + output.append("\n" + spaces + String.format("%1$-30s %2$13s", "Total:", toStringTotalCost())); + return output.toString(); + } + + @Override + public String toString() { + StringBuilder output = new StringBuilder(); + for (Expense e : expenseList) { + output.append(e.toString() + "\n"); + } + return output.toString(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Expenses // instanceof handles nulls + && expenseList.equals(((Expenses) other).expenseList)); // state check + } + + @Override + public int hashCode() { + return expenseList.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/expenses/Money.java b/src/main/java/seedu/address/model/expenses/Money.java new file mode 100644 index 000000000000..5a60063bea31 --- /dev/null +++ b/src/main/java/seedu/address/model/expenses/Money.java @@ -0,0 +1,118 @@ +package seedu.address.model.expenses; + +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Objects; + +import seedu.address.model.expenses.exceptions.MoneyLimitExceededException; + +/** + * Class to represent monetary values. + * Ensures that prices keyed in for Expense objects are valid. + * Range of dollars limited to +/- Integer.MAX_VALUE. + * Range does not include Integer.MIN_VALUE to prevent errors when changing signs. + */ +public class Money { + + public static final String MESSAGE_MONEY_CONSTRAINTS = + "Money should be written with at least one dollars digit and two cents digits.\n" + + "The magnitude of each amount is limited to 2^31 - 1.\n" + + "Unnecessary leading zeroes are also not allowed."; + + private int dollars; + private int cents; + // TODO implement exchange rates + + public Money(int dollars, int cents) { + if (cents >= 100 || cents < 0 || dollars == Integer.MIN_VALUE) { + throw new IllegalArgumentException(); + } + this.dollars = dollars; + this.cents = cents; + } + + /** + * Converts a string with valid money format into a Money object. + * @param toConvert The string to convert to a Money object. + */ + public Money(String toConvert) { + checkArgument(isValidMoneyFormat(toConvert), MESSAGE_MONEY_CONSTRAINTS); + String[] parts = toConvert.split("\\."); + dollars = Integer.parseInt(parts[0]); + cents = Integer.parseInt(parts[1]); + } + + /** + * Checks that a string is a valid representation of money. + * A valid string representation must be of the format "xxx.yy", + * i.e. must always represent with two decimal places. + * String cannot contain unnecessary leading zeroes, e.g. "012.34". + * Actual magnitude of monetary value cannot exceed Integer.MAX_VALUE. + * Negative amounts are accepted. + * @param money The string to be checked. + * @return True if the string is in money format, false otherwise. + */ + public static boolean isValidMoneyFormat(String money) { + // check 1: if negative value, make a new string without negative sign + String positiveMoney = money; + if (money.toCharArray()[0] == '-') { + positiveMoney = money.substring(1); + } + + // check 2: if starts with 0, should be 4 chars long, e.g. 0.45 + if (positiveMoney.toCharArray()[0] == '0' && positiveMoney.length() != 4) { + return false; + } + + // check 3: regex checking + String regex = "\\d{1,10}\\.\\d{2}"; + if (!positiveMoney.matches(regex)) { + return false; + } + + // check 4: range checking + // assume BigDecimal and BigInteger can hold arbitrarily large numbers + BigDecimal d = new BigDecimal(positiveMoney); + BigInteger i = d.toBigInteger(); + if (i.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) == 1) { + return false; + } + + return true; + } + + /** + * Creates a new Money object that is the sum of this object and another object. + * @param addend The Money object to be added to this Money object. + * @return The Money object representing the sum of the two Money objects. + */ + public Money add(Money addend) { + BigDecimal first = new BigDecimal(this.toString()); + BigDecimal second = new BigDecimal(addend.toString()); + BigDecimal sum = first.add(second); + if (sum.abs().toBigInteger().compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) == 1) { + throw new MoneyLimitExceededException(); + } + return new Money(sum.setScale(2).toString()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Money // instanceof handles nulls + && dollars == ((Money) other).dollars + && cents == ((Money) other).cents); // state check + } + + @Override + public String toString() { + return dollars + "." + String.format("%02d", cents); + } + + @Override + public int hashCode() { + return Objects.hash(dollars, cents); + } +} diff --git a/src/main/java/seedu/address/model/expenses/exceptions/ItemNotFoundException.java b/src/main/java/seedu/address/model/expenses/exceptions/ItemNotFoundException.java new file mode 100644 index 000000000000..3af143afe5be --- /dev/null +++ b/src/main/java/seedu/address/model/expenses/exceptions/ItemNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.expenses.exceptions; + +/** + * Indicates that the item number provided does not exist in the menu. + */ +public class ItemNotFoundException extends RuntimeException { + public ItemNotFoundException() { + super("The item was not found in the menu."); + } +} diff --git a/src/main/java/seedu/address/model/expenses/exceptions/MoneyLimitExceededException.java b/src/main/java/seedu/address/model/expenses/exceptions/MoneyLimitExceededException.java new file mode 100644 index 000000000000..4f528cac3a3e --- /dev/null +++ b/src/main/java/seedu/address/model/expenses/exceptions/MoneyLimitExceededException.java @@ -0,0 +1,11 @@ +package seedu.address.model.expenses.exceptions; + +/** + * Exception to indicate that two Money objects cannot be added together + * as the sum will exceed the accepted range (+/- Integer.MAX_VALUE). + */ +public class MoneyLimitExceededException extends RuntimeException { + public MoneyLimitExceededException() { + super("The resulting sum of money is too large."); + } +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/guest/Email.java similarity index 96% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/seedu/address/model/guest/Email.java index 38a7629e9a2d..a985d04ae00b 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/guest/Email.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.guest; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Guest's email in Concierge. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { diff --git a/src/main/java/seedu/address/model/guest/Guest.java b/src/main/java/seedu/address/model/guest/Guest.java new file mode 100644 index 000000000000..aabbb304e948 --- /dev/null +++ b/src/main/java/seedu/address/model/guest/Guest.java @@ -0,0 +1,129 @@ +package seedu.address.model.guest; + +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 Guest in Concierge. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Guest implements Comparable { + + // Identity fields + private final Name name; + private final Phone phone; + private final Email email; + + // Data fields + private final Set tags = new HashSet<>(); + + /** + * Every field must be present and not null. + */ + public Guest(Name name, Phone phone, Email email, Set tags) { + requireAllNonNull(name, phone, email, tags); + this.name = name; + this.phone = phone; + this.email = email; + this.tags.addAll(tags); + } + + public Guest(Guest toBeCopied) { + this.name = new Name(toBeCopied.getName().toString()); + this.phone = new Phone(toBeCopied.getPhone().toString()); + this.email = new Email(toBeCopied.getEmail().toString()); + this.tags.addAll(toBeCopied.getTags()); + } + + public Name getName() { + return name; + } + + public Phone getPhone() { + return phone; + } + + public Email getEmail() { + return email; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + /** + * Returns true if both guests of the same name have at least one other identity field that is the same. + * This defines a weaker notion of equality between two guests. + */ + public boolean isSameGuest(Guest otherGuest) { + if (otherGuest == this) { + return true; + } + + return otherGuest != null + && otherGuest.getName().equals(getName()) + && (otherGuest.getPhone().equals(getPhone()) || otherGuest.getEmail().equals(getEmail())); + } + + /** + * Returns true if both guests have the same identity and data fields. + * This defines a stronger notion of equality between two guests. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Guest)) { + return false; + } + + Guest otherGuest = (Guest) other; + return otherGuest.getName().equals(getName()) + && otherGuest.getPhone().equals(getPhone()) + && otherGuest.getEmail().equals(getEmail()) + && otherGuest.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, tags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" | Phone: ") + .append(getPhone()) + .append(" | Email: ") + .append(getEmail()); + return builder.toString(); + } + + @Override + public int compareTo(Guest other) { + int result = name.toString().compareTo(other.name.toString()); + if (result != 0) { + return result; + } + result = phone.toString().compareTo(other.phone.toString()); + if (result != 0) { + return result; + } + return email.toString().compareTo(other.email.toString()); + } + +} diff --git a/src/main/java/seedu/address/model/guest/GuestEmailExactPredicate.java b/src/main/java/seedu/address/model/guest/GuestEmailExactPredicate.java new file mode 100644 index 000000000000..a5c5e603514a --- /dev/null +++ b/src/main/java/seedu/address/model/guest/GuestEmailExactPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.guest; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Guest}'s {@code email} exactly matches {@code email} argument. + */ +public class GuestEmailExactPredicate implements Predicate { + private final Email email; + + public GuestEmailExactPredicate(Email email) { + this.email = email; + } + + @Override + public boolean test(Guest guest) { + return guest.getEmail().equals(email); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GuestEmailExactPredicate // instanceof handles nulls + && email.equals(((GuestEmailExactPredicate) other).email)); // state check + } + + @Override + public int hashCode() { + return email.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/guest/GuestNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/guest/GuestNameContainsKeywordsPredicate.java new file mode 100644 index 000000000000..5ebf8745aec1 --- /dev/null +++ b/src/main/java/seedu/address/model/guest/GuestNameContainsKeywordsPredicate.java @@ -0,0 +1,35 @@ +package seedu.address.model.guest; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Guest}'s {@code Name} matches any of the keywords given. + */ +public class GuestNameContainsKeywordsPredicate implements Predicate { + private final List names; + + public GuestNameContainsKeywordsPredicate(List names) { + this.names = names; + } + + @Override + public boolean test(Guest guest) { + return names.stream() + .anyMatch(name -> StringUtil.containsWordIgnoreCase(guest.getName().fullName, name.fullName)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GuestNameContainsKeywordsPredicate // instanceof handles nulls + && names.equals(((GuestNameContainsKeywordsPredicate) other).names)); // state check + } + + @Override + public int hashCode() { + return names.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/guest/GuestPhoneExactPredicate.java b/src/main/java/seedu/address/model/guest/GuestPhoneExactPredicate.java new file mode 100644 index 000000000000..ec13b9b90e71 --- /dev/null +++ b/src/main/java/seedu/address/model/guest/GuestPhoneExactPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.guest; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Guest}'s {@code phoneNumber} exactly matches {@code phoneNumber} argument. + */ +public class GuestPhoneExactPredicate implements Predicate { + private final Phone phone; + + public GuestPhoneExactPredicate(Phone phone) { + this.phone = phone; + } + + @Override + public boolean test(Guest guest) { + return guest.getPhone().equals(phone); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GuestPhoneExactPredicate // instanceof handles nulls + && phone.equals(((GuestPhoneExactPredicate) other).phone)); // state check + } + + @Override + public int hashCode() { + return phone.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/guest/GuestTagsContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/guest/GuestTagsContainsKeywordsPredicate.java new file mode 100644 index 000000000000..5e1dcbf7fa69 --- /dev/null +++ b/src/main/java/seedu/address/model/guest/GuestTagsContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.guest; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.model.tag.Tag; + +/** + * Tests that a {@code Guest}'s {@code tags} exactly matches a single {@code tag} keyword argument. + */ +public class GuestTagsContainsKeywordsPredicate implements Predicate { + private final List tags; + + public GuestTagsContainsKeywordsPredicate(List tags) { + this.tags = tags; + } + + @Override + public boolean test(Guest guest) { + List guestTags = new LinkedList<>(guest.getTags()); + + for (Tag tag : tags) { + for (Tag guestTag : guestTags) { + if (guestTag.tagName.equalsIgnoreCase(tag.tagName)) { + return true; + } + } + } + + return false; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GuestTagsContainsKeywordsPredicate // instanceof handles nulls + && tags.equals(((GuestTagsContainsKeywordsPredicate) other).tags)); // state check + } + + @Override + public int hashCode() { + return tags.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/guest/Name.java similarity index 90% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/guest/Name.java index 9982393dabb5..9505889e2e72 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/guest/Name.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.guest; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Guest's name in Concierge. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { @@ -13,7 +13,7 @@ public class Name { "Names should only contain alphanumeric characters and spaces, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the name must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/guest/Phone.java similarity index 93% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/address/model/guest/Phone.java index a22e51653835..3a7e3d43be8e 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/guest/Phone.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.guest; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Guest's phone number in Concierge. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { diff --git a/src/main/java/seedu/address/model/guest/UniqueGuestList.java b/src/main/java/seedu/address/model/guest/UniqueGuestList.java new file mode 100644 index 000000000000..be0d730ad8bb --- /dev/null +++ b/src/main/java/seedu/address/model/guest/UniqueGuestList.java @@ -0,0 +1,118 @@ +package seedu.address.model.guest; + +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.guest.exceptions.DuplicateGuestException; +import seedu.address.model.guest.exceptions.GuestNotFoundException; + +/** + * A list of guests that enforces uniqueness between its elements and does not allow nulls. + * A guest is considered unique by comparing using {@code Guest#isSameGuest(Guest)}. As such, adding and updating of + * guests uses Guest#isSameGuest(Guest) for equality so as to ensure that the guest being added or updated is + * unique in terms of identity in the UniqueGuestList. However, the removal of a guest uses Guest#equals(Object) so + * as to ensure that the guest with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Guest#isSameGuest(Guest) + */ +public class UniqueGuestList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent guest as the given argument. + */ + public boolean contains(Guest toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameGuest); + } + + /** + * Adds a guest to the list. + * The guest must not already exist in the list. + */ + public void add(Guest toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateGuestException(); + } + internalList.add(toAdd); + } + + /** + * Removes the equivalent guest from the list. + * The guest must exist in the list. + */ + public void remove(Guest toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new GuestNotFoundException(); + } + } + + /** + * Replaces the contents of this list with {@code replacement}. + */ + public void setGuests(UniqueGuestList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code guests}. + * {@code guests} must not contain duplicate guests. + */ + public void setGuests(List guests) { + requireAllNonNull(guests); + if (!guestsAreUnique(guests)) { + throw new DuplicateGuestException(); + } + + internalList.setAll(guests); + } + + /** + * 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 UniqueGuestList // instanceof handles nulls + && internalList.equals(((UniqueGuestList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code guests} contains only unique guests. + */ + private boolean guestsAreUnique(List guests) { + for (int i = 0; i < guests.size() - 1; i++) { + for (int j = i + 1; j < guests.size(); j++) { + if (guests.get(i).isSameGuest(guests.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/guest/exceptions/DuplicateGuestException.java b/src/main/java/seedu/address/model/guest/exceptions/DuplicateGuestException.java new file mode 100644 index 000000000000..680bf5c0336d --- /dev/null +++ b/src/main/java/seedu/address/model/guest/exceptions/DuplicateGuestException.java @@ -0,0 +1,11 @@ +package seedu.address.model.guest.exceptions; + +/** + * Signals that the operation will result in Guests. + * Guests are considered duplicates if they have the same identity). + */ +public class DuplicateGuestException extends RuntimeException { + public DuplicateGuestException() { + super("Operation would result in duplicate guests"); + } +} diff --git a/src/main/java/seedu/address/model/guest/exceptions/GuestNotFoundException.java b/src/main/java/seedu/address/model/guest/exceptions/GuestNotFoundException.java new file mode 100644 index 000000000000..fc42c0ed5111 --- /dev/null +++ b/src/main/java/seedu/address/model/guest/exceptions/GuestNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.guest.exceptions; + +/** + * Signals that the operation is unable to find the specified guest. + */ +public class GuestNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/login/InvalidLogInException.java b/src/main/java/seedu/address/model/login/InvalidLogInException.java new file mode 100644 index 000000000000..410bf14037ff --- /dev/null +++ b/src/main/java/seedu/address/model/login/InvalidLogInException.java @@ -0,0 +1,16 @@ +package seedu.address.model.login; + +/** + * Signals that the password supplied does not match the user name. + */ +public class InvalidLogInException extends RuntimeException { + + public static final String MESSAGE_INVALID_USERNAME = + "The username supplied is invalid."; + public static final String MESSAGE_INVALID_PASSWORD = + "The password does not match the username supplied."; + + public InvalidLogInException(String msg) { + super(msg); + } +} diff --git a/src/main/java/seedu/address/model/login/InvalidLogOutException.java b/src/main/java/seedu/address/model/login/InvalidLogOutException.java new file mode 100644 index 000000000000..f8c7af1dafc7 --- /dev/null +++ b/src/main/java/seedu/address/model/login/InvalidLogOutException.java @@ -0,0 +1,12 @@ +package seedu.address.model.login; + +/** + * Represents an invalid logout attempt. + */ +public class InvalidLogOutException extends Exception { + + public InvalidLogOutException() { + super("You are not logged in and hence cannot logout."); + } + +} diff --git a/src/main/java/seedu/address/model/login/LogInManager.java b/src/main/java/seedu/address/model/login/LogInManager.java new file mode 100644 index 000000000000..6119bc994ca7 --- /dev/null +++ b/src/main/java/seedu/address/model/login/LogInManager.java @@ -0,0 +1,99 @@ +package seedu.address.model.login; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.login.InvalidLogInException.MESSAGE_INVALID_PASSWORD; +import static seedu.address.model.login.InvalidLogInException.MESSAGE_INVALID_USERNAME; + +import java.util.Optional; + +import seedu.address.logic.commands.LogInCommand; + +/** + * A helper class to manage the login and logout process. + */ +public class LogInManager { + + // The username currently associated with the session + private Optional username = Optional.empty(); + + // The password reference list of key-value pairs + private final PasswordHashList passwordRef; + + /** + * Creates an {@code LogInHelper} with an empty password reference list. + */ + public LogInManager() { + this.passwordRef = new PasswordHashList(); + } + + public LogInManager(PasswordHashList passwordRef) { + requireNonNull(passwordRef); + this.passwordRef = passwordRef; + } + + /** + * Checks if the current {@code LogInHelper} is signed in. + */ + public boolean isSignedIn() { + return this.username.isPresent(); + } + + /** + * Returns an Optional of the username associated with the session. + * Returns Optional.empty() if the session is not signed in. + */ + public Optional getUsername() { + return this.username; + } + + /** + * Attempts to sign in with the given {@code username} and the + * {@code hashed password}. + * + * @throws InvalidLogInException when the username is not present, + * or the password does not match the username. + */ + public void signIn(String username, String hashedPassword) throws InvalidLogInException { + requireNonNull(username, hashedPassword); + + if (isSignedIn()) { + throw new InvalidLogInException(LogInCommand.MESSAGE_SIGNED_IN_ALREADY); + } + + Optional expectedPassword = passwordRef.getExpectedPassword(username); + + if (!expectedPassword.isPresent()) { + // No match found for the given username + throw new InvalidLogInException(MESSAGE_INVALID_USERNAME); + } else if (!hashedPassword.equalsIgnoreCase(expectedPassword.get())) { + throw new InvalidLogInException(MESSAGE_INVALID_PASSWORD); + } + + this.username = Optional.of(username); + } + + /** + * Attempts to sign out of Concierge. + * + * @throws InvalidLogOutException if Concierge is not signed in. + */ + public void signOut() throws InvalidLogOutException { + if (!this.isSignedIn()) { + throw new InvalidLogOutException(); + } + + this.username = Optional.empty(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (!(other instanceof LogInManager)) { + return false; + } + + return ((LogInManager) other).getUsername().equals(getUsername()) + && ((LogInManager) other).passwordRef.equals(passwordRef); + } +} diff --git a/src/main/java/seedu/address/model/login/PasswordHashList.java b/src/main/java/seedu/address/model/login/PasswordHashList.java new file mode 100644 index 000000000000..10a21e06f01e --- /dev/null +++ b/src/main/java/seedu/address/model/login/PasswordHashList.java @@ -0,0 +1,96 @@ +package seedu.address.model.login; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.PasswordHashUtil.hash; + +import java.util.Optional; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Represents a key-value reference list for usernames and passwords, and is + * used in the {@code LogInHelper}. + */ +public class PasswordHashList { + + // Name of the JsonNode variable, used to extract the key-value pairs + private static final String PASSWORD_REF_NAME = "passwordReferenceList"; + + // The JSON password reference list, also the JSON key that wraps the other key-value pairs + private JsonNode passwordReferenceList; + + /** + * Returns an empty password hash list with no username or passwords + */ + public PasswordHashList() { + this.passwordReferenceList = JsonNodeFactory.instance.objectNode(); + } + + /** + * This method creates a new PasswordHashList using a JsonNode with a + * {@code PASSWORD_REF_NAME} wrapper key. This facilitates the + * serialising and storage component handled by {@code JsonUtil}. + * @param passwordReferenceList + */ + public PasswordHashList(JsonNode passwordReferenceList) { + requireNonNull(passwordReferenceList); + this.passwordReferenceList = passwordReferenceList.get(PASSWORD_REF_NAME); + } + + /** + * Returns a new {@code PasswordHashList} with the username and password + * entry added. Currently used for testing, but could be extended to + * allow users to add new accounts. + */ + public PasswordHashList addEntry(String username, String password) { + requireNonNull(username, password); + + // JsonNode is read-only, while ObjectNode can be written to + ObjectNode newPasswordRef = (ObjectNode) passwordReferenceList; + newPasswordRef.put(username, hash(password)); + + // The current constructor that takes in a JsonNode object expects a + // wrapper {@code PASSWORD_REF_NAME} key. + PasswordHashList passwordHashList = new PasswordHashList(); + passwordHashList.passwordReferenceList = passwordReferenceList; + + return passwordHashList; + } + + /** + * Retrieves the password associated with the given username. + * @param username The username which is the key + * @return An Optional value which is empty if the username is not + * present. Else, the optional will wrap the associated password. + */ + protected Optional getExpectedPassword(String username) { + requireNonNull(username); + + JsonNode expectedPasswordNode = passwordReferenceList.get(username); + + if (expectedPasswordNode == null) { + return Optional.empty(); + } + + return Optional.of(expectedPasswordNode.asText()); + } + + @Override + public String toString() { + return passwordReferenceList.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (!(other instanceof PasswordHashList)) { + return false; + } + + return ((PasswordHashList) other).passwordReferenceList.equals(passwordReferenceList); + } + +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index a1409233ceb9..000000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,58 +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 address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_ADDRESS_CONSTRAINTS = - "Addresses can take any values, and it should not be blank"; - - /* - * 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 final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_ADDRESS_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(ADDRESS_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427ca..000000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} 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/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f594423..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} 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/room/Capacity.java b/src/main/java/seedu/address/model/room/Capacity.java new file mode 100644 index 000000000000..b6c3d07d3cfb --- /dev/null +++ b/src/main/java/seedu/address/model/room/Capacity.java @@ -0,0 +1,57 @@ +package seedu.address.model.room; + +/** + * Represents a room's capacity in the hotel. + * Guarantees: immutable; is valid as declared in the constants of this enum + */ +public enum Capacity { + SINGLE(1), + DOUBLE(2), + SUITE(5); + + public static final String MESSAGE_CAPACITY_CONSTRAINTS = + "Capacity should only be SINGLE, DOUBLE, or SUITE. SINGLE is of integer value 1, DOUBLE is 2, SUITE is 5."; + + public final int value; + + /** + * Constructs a {@code Name}. + * + * @param value A valid name. + */ + Capacity(int value) { + this.value = value; + } + + /** + * Gets the integer value of this capacity + */ + public int getValue() { + return value; + } + + /** + * Returns true if a given string is a valid capacity. + */ + public static boolean isValidCapacity(String test) { + int value = 0; + + if (test.length() > 1) { + return false; + } + + try { + value = Integer.parseInt(test); + } catch (NumberFormatException e) { + return false; + } + + return SINGLE.value == value || DOUBLE.value == value || SUITE.value == value; + } + + @Override + public String toString() { + return String.format("%d | %s", value, this.name()); + } + +} diff --git a/src/main/java/seedu/address/model/room/Room.java b/src/main/java/seedu/address/model/room/Room.java new file mode 100644 index 000000000000..78f278bc5cc5 --- /dev/null +++ b/src/main/java/seedu/address/model/room/Room.java @@ -0,0 +1,225 @@ +package seedu.address.model.room; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.expenses.Expense; +import seedu.address.model.expenses.Expenses; +import seedu.address.model.room.booking.Booking; +import seedu.address.model.room.booking.Bookings; +import seedu.address.model.room.booking.exceptions.BookingAlreadyCheckedInException; +import seedu.address.model.room.booking.exceptions.ExpiredBookingException; +import seedu.address.model.room.booking.exceptions.InactiveBookingCheckInException; +import seedu.address.model.room.booking.exceptions.NoBookingException; +import seedu.address.model.room.booking.exceptions.RoomNotCheckedInException; +import seedu.address.model.tag.Tag; + +/** + * Represents a room in the hotel. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Room { + + // Identity fields + public final RoomNumber roomNumber; + + // Data fields + public final Capacity capacity; + public final Expenses expenses; + public final Bookings bookings; + public final Set tags; + + public Room(RoomNumber roomNumber, Capacity capacity) { + this(roomNumber, capacity, new Expenses(), new Bookings(), new HashSet<>()); + } + + /** + * All parameters must be non-null. + */ + public Room(RoomNumber roomNumber, Capacity capacity, Expenses expenses, Bookings bookings, Set tags) { + requireAllNonNull(roomNumber, capacity, expenses, bookings, tags); + this.roomNumber = roomNumber; + this.capacity = capacity; + this.expenses = expenses; + this.bookings = bookings; + this.tags = tags; + } + + //=========== Getters ============================================================= + + public RoomNumber getRoomNumber() { + return roomNumber; + } + + public Capacity getCapacity() { + return capacity; + } + + public Expenses getExpenses() { + return expenses; + } + + public Bookings getBookings() { + return bookings; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + //=========== Tags operations ============================================================= + + /** + * Returns a copy of this room with given tags added + */ + public Room addTags(Tag... tags) { + Set editedTags = new HashSet<>(this.tags); + editedTags.addAll(Arrays.asList(tags)); + return new Room(this.roomNumber, this.capacity, this.expenses, this.bookings, editedTags); + } + + //=========== Bookings operations ============================================================= + + /** + * Adds a booking to a copy of this room's set of bookings + */ + public Room addBooking(Booking booking) { + Bookings editedBookings = bookings.add(booking); + return new Room(this.roomNumber, this.capacity, this.expenses, editedBookings, this.tags); + } + + /** + * Update a booking with the edited booking + */ + private Room updateBooking(Booking target, Booking editedBooking) { + Bookings editedBookings = bookings.updateBooking(target, editedBooking); + return new Room(this.roomNumber, this.capacity, this.expenses, editedBookings, this.tags); + } + + /** + * Checks in the first booking of this room, only if: + * 1) it is not expired + * 2) it is active + * 3) it is not already checked-in + */ + public Room checkIn() { + Booking firstBooking = bookings.getFirstBooking(); + if (firstBooking.isExpired()) { + throw new ExpiredBookingException(); + } + if (!firstBooking.isActive()) { + throw new InactiveBookingCheckInException(); + } + if (firstBooking.getIsCheckedIn()) { + throw new BookingAlreadyCheckedInException(); + } + return updateBooking(firstBooking, firstBooking.checkIn()); + } + + /** + * Checks out the given booking + */ + public Room checkout(Booking bookingToCheckout) { + return new Room(this.roomNumber, this.capacity, this.expenses.clearExpenses(), + bookings.remove(bookingToCheckout), this.tags); + } + + //=========== Expenses operations ============================================================= + + /** + * Add an expense to this room's expenses + */ + public Room addExpense(Expense expense) { + if (bookings.getSortedBookingsSet().isEmpty()) { + // no bookings + throw new NoBookingException(); + } + if (!bookings.getFirstBooking().getIsCheckedIn()) { + // not checked in + throw new RoomNotCheckedInException(); + } + Expenses editedExpenses = expenses.addExpense(expense); + return new Room(this.roomNumber, this.capacity, editedExpenses, this.bookings, this.tags); + } + + //=========== Boolean checkers ============================================================= + + /** + * Returns true if this room has bookings + */ + public boolean hasBookings() { + return !bookings.getSortedBookingsSet().isEmpty(); + } + + /** + * Returns true if both rooms of the same name have the same room number. + * This defines a weaker notion of equality between two rooms. + */ + public boolean isSameRoom(Room otherRoom) { + if (otherRoom == this) { + return true; + } + + return otherRoom != null + && otherRoom.getRoomNumber().equals(getRoomNumber()); + } + + /** + * Returns true if both rooms have the same identity and data fields. + * This defines a stronger notion of equality between two rooms. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Room)) { + return false; + } + + Room otherRoom = (Room) other; + return otherRoom.getRoomNumber().equals(getRoomNumber()) + && otherRoom.getCapacity().equals(getCapacity()) + && otherRoom.getBookings().equals(getBookings()) + && otherRoom.getExpenses().equals(getExpenses()) + && otherRoom.getTags().equals(getTags()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(roomNumber, capacity, expenses, bookings, tags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Room: ") + .append(getRoomNumber()) + .append("\n") + .append("Capacity: ") + .append(getCapacity()) + .append("\n") + .append("Expenses: ") + .append(getExpenses()) + .append("\n") + .append("Bookings: ") + .append(getBookings()) + .append("\n") + .append("Tags: "); + getTags().forEach(builder::append); + builder.append("\n\n"); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/room/RoomBookingsDateRangePredicate.java b/src/main/java/seedu/address/model/room/RoomBookingsDateRangePredicate.java new file mode 100644 index 000000000000..f840cc7e76b4 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomBookingsDateRangePredicate.java @@ -0,0 +1,42 @@ +package seedu.address.model.room; + +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.model.room.booking.Booking; +import seedu.address.model.room.booking.BookingPeriod; + +/** + * Tests that a {@code Room}'s {@code BookingPeriod} matches the range of the specified {@code BookingPeriod} argument. + */ +public class RoomBookingsDateRangePredicate implements Predicate { + private final BookingPeriod bookingPeriod; + + public RoomBookingsDateRangePredicate(BookingPeriod bookingPeriod) { + this.bookingPeriod = bookingPeriod; + } + + @Override + public boolean test(Room room) { + Set setRoomBooking = room.getBookings().getSortedBookingsSet(); + + for (Booking booking : setRoomBooking) { + if (!booking.getBookingPeriod().isOverlapping(bookingPeriod)) { + return false; + } + } + return true; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RoomBookingsDateRangePredicate // instanceof handles nulls + && bookingPeriod.equals(((RoomBookingsDateRangePredicate) other).bookingPeriod)); // state check + } + + @Override + public int hashCode() { + return bookingPeriod.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/room/RoomCapacityExactPredicate.java b/src/main/java/seedu/address/model/room/RoomCapacityExactPredicate.java new file mode 100644 index 000000000000..792cde2674d8 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomCapacityExactPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.room; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Room}'s {@code Capacity} exactly matches {@code capacity} argument. + */ +public class RoomCapacityExactPredicate implements Predicate { + private final String capacity; + + public RoomCapacityExactPredicate(String capacity) { + this.capacity = capacity; + } + + @Override + public boolean test(Room room) { + return room.getCapacity().value == Integer.parseInt(capacity); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RoomCapacityExactPredicate // instanceof handles nulls + && capacity.equals(((RoomCapacityExactPredicate) other).capacity)); // state check + } + @Override + public int hashCode() { + return capacity.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/room/RoomHasBookingWithGuestExactPredicate.java b/src/main/java/seedu/address/model/room/RoomHasBookingWithGuestExactPredicate.java new file mode 100644 index 000000000000..60127227ca5a --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomHasBookingWithGuestExactPredicate.java @@ -0,0 +1,47 @@ +package seedu.address.model.room; + +import java.util.List; +import java.util.SortedSet; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.guest.Name; +import seedu.address.model.room.booking.Booking; + +/** + * Tests that a {@code Room}'s {@code BookingPeriod} matches the range of the specified {@code BookingPeriod} argument. + */ +public class RoomHasBookingWithGuestExactPredicate implements Predicate { + private final List names; + + public RoomHasBookingWithGuestExactPredicate(List names) { + this.names = names; + } + + @Override + public boolean test(Room room) { + SortedSet allBookings = room.bookings.getSortedBookingsSet(); + + for (Booking booking : allBookings) { + if (names.stream() + .anyMatch(name -> StringUtil.containsWordIgnoreCase( + booking.getGuest().getName().fullName, name.fullName))) { + return true; + } + } + + return false; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RoomHasBookingWithGuestExactPredicate // instanceof handles nulls + && names.equals(((RoomHasBookingWithGuestExactPredicate) other).names)); // state check + } + + @Override + public int hashCode() { + return names.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/room/RoomHasBookingsExactPredicate.java b/src/main/java/seedu/address/model/room/RoomHasBookingsExactPredicate.java new file mode 100644 index 000000000000..b72d10d24d75 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomHasBookingsExactPredicate.java @@ -0,0 +1,32 @@ +package seedu.address.model.room; + +import java.util.Objects; +import java.util.function.Predicate; + +/** + * Tests that a {@code Room} currently has valid, or has no {@code Bookings}. + */ +public class RoomHasBookingsExactPredicate implements Predicate { + private final boolean hasBookings; + + public RoomHasBookingsExactPredicate(boolean hasBookings) { + this.hasBookings = hasBookings; + } + + @Override + public boolean test(Room room) { + return room.getBookings().getSortedBookingsSet().isEmpty() == !hasBookings; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RoomHasBookingsExactPredicate // instanceof handles nulls + && hasBookings == ((RoomHasBookingsExactPredicate) other).hasBookings); // state check + } + + @Override + public int hashCode() { + return Objects.hashCode(hasBookings); + } +} diff --git a/src/main/java/seedu/address/model/room/RoomNumber.java b/src/main/java/seedu/address/model/room/RoomNumber.java new file mode 100644 index 000000000000..ef92d55d48a2 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomNumber.java @@ -0,0 +1,66 @@ +package seedu.address.model.room; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import seedu.address.commons.core.index.Index; + +/** + * Represents a Room's room number in Concierge. + * Guarantees: immutable; is valid as declared in {@link #isValidRoomNumber(String)} + */ +public class RoomNumber { + + public static final String MAX_ROOM_NUMBER = "100"; + public static final String MESSAGE_ROOM_NUMBER_CONSTRAINTS = + String.format("Room Number should be a string that only contain numbers from 001 to %s, and it should " + + "not be blank", MAX_ROOM_NUMBER); + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String ROOM_NUMBER_VALIDATION_REGEX = "^(0\\d[1-9]|0[1-9]\\d|100)$"; + + public final String value; + + /** + * Constructs a {@code id}. + * + * @param value A valid room number. + */ + public RoomNumber(String value) { + requireNonNull(value); + checkArgument(isValidRoomNumber(value), MESSAGE_ROOM_NUMBER_CONSTRAINTS); + this.value = value; + } + + /** + * Returns true if a given string is a valid room number. + */ + public static boolean isValidRoomNumber(String test) { + return test.matches(ROOM_NUMBER_VALIDATION_REGEX); + } + + public Index getRoomNumberAsIndex() { + return Index.fromOneBased(Integer.parseInt(value)); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RoomNumber // instanceof handles nulls + && value.equals(((RoomNumber) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/room/RoomNumberExactPredicate.java b/src/main/java/seedu/address/model/room/RoomNumberExactPredicate.java new file mode 100644 index 000000000000..e39ef4043099 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomNumberExactPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.room; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Room}'s {@code RoomNumber} exactly matches {@code RoomNumber} argument. + */ +public class RoomNumberExactPredicate implements Predicate { + private final RoomNumber roomNumber; + + public RoomNumberExactPredicate(RoomNumber roomNumber) { + this.roomNumber = roomNumber; + } + + @Override + public boolean test(Room room) { + return room.getRoomNumber().equals(roomNumber); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RoomNumberExactPredicate // instanceof handles nulls + && roomNumber.equals(((RoomNumberExactPredicate) other).roomNumber)); // state check + } + + @Override + public int hashCode() { + return roomNumber.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/room/RoomTagsContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/room/RoomTagsContainsKeywordsPredicate.java new file mode 100644 index 000000000000..1d7f40a9cb4d --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomTagsContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.room; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.model.tag.Tag; + +/** + * Tests that any of a {@code Room}'s {@code tags} exactly matches {@code keyword} argument for tags. + */ +public class RoomTagsContainsKeywordsPredicate implements Predicate { + private final List tags; + + public RoomTagsContainsKeywordsPredicate(List tags) { + this.tags = tags; + } + + @Override + public boolean test(Room room) { + List roomTags = new LinkedList<>(room.getTags()); + + for (Tag tag : tags) { + for (Tag roomTag : roomTags) { + if (roomTag.tagName.equalsIgnoreCase(tag.tagName)) { + return true; + } + } + } + + return false; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RoomTagsContainsKeywordsPredicate // instanceof handles nulls + && tags.equals(((RoomTagsContainsKeywordsPredicate) other).tags)); // state check + } + + @Override + public int hashCode() { + return tags.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/room/UniqueRoomList.java b/src/main/java/seedu/address/model/room/UniqueRoomList.java new file mode 100644 index 000000000000..ffce80e3842e --- /dev/null +++ b/src/main/java/seedu/address/model/room/UniqueRoomList.java @@ -0,0 +1,164 @@ +package seedu.address.model.room; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.room.exceptions.DuplicateRoomException; +import seedu.address.model.room.exceptions.RoomMissingException; +import seedu.address.model.room.exceptions.RoomNotFoundException; + +/** + * A list of rooms that enforces uniqueness between its elements and does not allow nulls. + * A room is considered unique by comparing using {@code Room#isSameRoom(Room)}. As such, adding and updating of + * rooms uses Room#isSameRoom(Room) for equality so as to ensure that the room being added or updated is + * unique in terms of identity in the UniqueRoomList. A room can be removed by passing either its reference or + * room number to the remove() method. + * + * Supports a minimal set of list operations. + * + * @see Room#isSameRoom(Room) + */ +public class UniqueRoomList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Initializes a room list with default RoomNumber.MAX_ROOM_NUMBER + */ + public UniqueRoomList() { + this(RoomNumber.MAX_ROOM_NUMBER); + } + + /** + * Initializes a room list with rooms ranging from 001 up to the given maxRoomNumber. + * Note: the maxRoomNumber here IS LIMITED BY Room.MAX_ROOM_NUMBER + * @param maxRoomNumber The maximum room number as a string + */ + private UniqueRoomList(String maxRoomNumber) { + this.internalList.setAll(Stream.iterate(1, i -> i <= Integer.parseInt(maxRoomNumber), i -> i + 1) + .map(i -> { + RoomNumber roomNumber = new RoomNumber(String.format("%03d", i)); + if (i % 10 == 0) { // All rooms with room number that is multiple of 10 is a Suite Room. + return new Room(roomNumber, Capacity.SUITE); + } + if (i % 2 == 0) { // All rooms with even room number is a Double Room. + return new Room(roomNumber, Capacity.DOUBLE); + } + // ALl rooms with odd room number is a Single Room. + return new Room(roomNumber, Capacity.SINGLE); + }) + .collect(Collectors.toList())); + } + //=========== Getters ============================================================= + + /** + * Returns the room according to the room number + */ + public Room getRoom(RoomNumber roomNumber) { + requireNonNull(roomNumber); + for (Room room : internalList) { + if (room.getRoomNumber().equals(roomNumber)) { + return room; + } + } + throw new RoomNotFoundException(); + } + + //=========== Operations ============================================================= + + /** + * Replaces the room {@code target} in the list with {@code editedRoom}. + * {@code target} must exist in the list. + * The room identity of {@code editedRoom} must not be the same as another existing room in the list. + */ + public void setRoom(Room target, Room editedRoom) { + requireAllNonNull(target, editedRoom); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new RoomNotFoundException(); + } + if (!target.isSameRoom(editedRoom) && contains(editedRoom)) { + throw new DuplicateRoomException(); + } + + internalList.set(index, editedRoom); + } + + /** + * Replaces the contents of this list with {@code rooms}. + * {@code rooms} must not contain duplicate rooms. + */ + public void setRooms(List rooms) { + requireAllNonNull(rooms); + if (!roomsAreUnique(rooms)) { + throw new DuplicateRoomException(); + } + if (rooms.size() != Integer.parseInt(RoomNumber.MAX_ROOM_NUMBER)) { + throw new RoomMissingException(); + } + internalList.setAll(rooms); + } + + //=========== Boolean checkers ============================================================= + + /** + * Returns true if the list contains an equivalent room as the given argument. + */ + public boolean contains(Room toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameRoom); + } + + /** + * 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 UniqueRoomList // instanceof handles nulls + && internalList.equals(((UniqueRoomList) other).internalList)); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + internalList.forEach(sb::append); + return sb.toString(); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code rooms} contains only unique rooms. + */ + private boolean roomsAreUnique(List rooms) { + for (int i = 0; i < rooms.size() - 1; i++) { + for (int j = i + 1; j < rooms.size(); j++) { + if (rooms.get(i).isSameRoom(rooms.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/room/booking/Booking.java b/src/main/java/seedu/address/model/room/booking/Booking.java new file mode 100644 index 000000000000..639e3e368e0c --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/Booking.java @@ -0,0 +1,175 @@ +package seedu.address.model.room.booking; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +import seedu.address.model.guest.Guest; + +/** + * Represents a Booking of a room in the hotel. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Booking implements Comparable { + + // Identity fields + private final Guest guest; + private final BookingPeriod bookingPeriod; + private final Boolean isCheckedIn; + + /** + * Guest and BookingPeriod must be present and not null. Constructs a non-checked-in booking by default + */ + public Booking(Guest guest, BookingPeriod bookingPeriod) { + this(guest, bookingPeriod, false); + } + + /** + * Constructs a booking with specified guest, booking period and checked in status + */ + public Booking(Guest guest, BookingPeriod bookingPeriod, Boolean isCheckedIn) { + requireAllNonNull(guest, bookingPeriod, isCheckedIn); + this.guest = guest; + this.bookingPeriod = bookingPeriod; + this.isCheckedIn = isCheckedIn; + } + + public Guest getGuest() { + return guest; + } + + public BookingPeriod getBookingPeriod() { + return bookingPeriod; + } + + public Boolean getIsCheckedIn() { + return isCheckedIn; + } + + /** + * Returns an edited booking that has been checked-in + * This is needed to maintain immutability of objects in Concierge + */ + public Booking checkIn() { + return new Booking(this.getGuest(), this.getBookingPeriod(), true); + } + + /** + * Checks if the {@code Booking} overlaps with the other. + */ + public boolean isOverlapping(Booking other) { + return bookingPeriod.isOverlapping(other.getBookingPeriod()); + } + + /** + * Checks if this booking is expired. + */ + public boolean isExpired() { + return getBookingPeriod().isExpired(); + } + + /** + * Checks if this booking is outdated. + */ + public boolean isOutdated() { + return getBookingPeriod().isOutdated(); + } + + /** + * Checks if this booking is active. + */ + public boolean isActive() { + return getBookingPeriod().isActive(); + } + + /** + * Returns true if both bookings have the same guest and booking period. + * This defines a weaker notion of equality between two rooms, as it does not need checkIn to be the same. + */ + public boolean isSameBooking(Booking otherBooking) { + if (otherBooking == this) { + return true; + } + + return otherBooking != null + && otherBooking.getGuest().equals(getGuest()) + && otherBooking.getBookingPeriod().equals(getBookingPeriod()); + } + + /** + * Returns true if this booking starts before the other + */ + public boolean startsBefore(Booking otherBooking) { + return this.bookingPeriod.startsBefore(otherBooking.bookingPeriod); + } + + /** + * Returns true if both bookings have the same identity and data fields. + * This defines a stronger notion of equality between two bookings. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Booking)) { + return false; + } + + Booking otherBooking = (Booking) other; + return otherBooking.getGuest().equals(getGuest()) + && otherBooking.getBookingPeriod().equals(getBookingPeriod()) + && (otherBooking.getIsCheckedIn().equals(getIsCheckedIn())); + } + + + /** + * Gets the short description of this room, which comprises of + * 1) Guest's name + * 2) Booking period + * 3) Checked-in status + */ + public String toStringShortDescription() { + final StringBuilder builder = new StringBuilder(); + builder.append("Booking period: ") + .append(getBookingPeriod()) + .append("\nGuest: ") + .append(getGuest().getName()) + .append("\nChecked-in: ") + .append(getIsCheckedIn() ? "Yes" : "No"); + return builder.toString(); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(guest, bookingPeriod); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Booking period: ") + .append(getBookingPeriod()) + .append("\nGuest: ") + .append(getGuest()) + .append("\nChecked-in: ") + .append(getIsCheckedIn() ? "Yes" : "No") + .append("\n"); + return builder.toString(); + } + + @Override + public int compareTo(Booking other) { + int result = bookingPeriod.compareTo(other.getBookingPeriod()); + if (result != 0) { + return result; + } + result = guest.compareTo(other.guest); + if (result != 0) { + return result; + } + return isCheckedIn.compareTo(other.isCheckedIn); + } +} diff --git a/src/main/java/seedu/address/model/room/booking/BookingPeriod.java b/src/main/java/seedu/address/model/room/booking/BookingPeriod.java new file mode 100644 index 000000000000..7c4e2aad1586 --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/BookingPeriod.java @@ -0,0 +1,161 @@ +package seedu.address.model.room.booking; + +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; +import java.util.Objects; + +/** + * Represents a Room's booking period in Concierge. + * Guarantees: immutable; is valid as declared in + * {@link #isValidBookingPeriod(String testStartDate,String testEndDate)} + */ +public class BookingPeriod implements Comparable { + + public static final String MESSAGE_BOOKING_PERIOD_CONSTRAINTS = + "Booking period takes in 2 dates, each of which should be in the form day/month/year. " + + "day can be 1 or 2 digits, month can be 1 or 2 digits, year can be 2 or 4 digits." + + "\nAlso, dates should be valid dates according to the Gregorian calendar," + + "and should not be blank or the same date."; + + /** + * Standard date format used for this hotel's bookings. + */ + public static final DateTimeFormatter STRING_TO_DATE_FORMAT = DateTimeFormatter.ofPattern("d/M/[uuuu][uu]") + .withResolverStyle(ResolverStyle.STRICT); + public static final DateTimeFormatter DATE_TO_STRING_FORMAT = DateTimeFormatter.ofPattern("d/M/u") + .withResolverStyle(ResolverStyle.STRICT); + + public final LocalDate startDate; + public final LocalDate endDate; + + /** + * Constructs a {@code BookingPeriod} that encapsulates the period from start through end date (inclusive). + */ + public BookingPeriod(String startDate, String endDate) { + requireAllNonNull(startDate, endDate); + checkArgument(isValidBookingPeriod(startDate, endDate), MESSAGE_BOOKING_PERIOD_CONSTRAINTS); + this.startDate = parseDate(startDate); + this.endDate = parseDate(endDate); + } + + public LocalDate getStartDate() { + return startDate; + } + + public LocalDate getEndDate() { + return endDate; + } + + public String getStartDateAsFormattedString() { + return startDate.format(DATE_TO_STRING_FORMAT); + } + + public String getEndDateAsFormattedString() { + return endDate.format(DATE_TO_STRING_FORMAT); + } + + /** + * Returns true if given string can be parsed into LocalDates using the formatter, + * and if startDate is strictly before endDate. Thus, startDate CANNOT BE EQUALS to endDate + */ + public static boolean isValidBookingPeriod(String startDate, String endDate) { + try { + LocalDate start = parseDate(startDate); + LocalDate end = parseDate(endDate); + return start.isBefore(end); + } catch (DateTimeParseException e) { + return false; + } + } + + /** + * Parses the given date to {@code }LocalDate} object. + * @param date A date of the correct format. + * @return {@code LocalDate} object representing the date. + */ + private static LocalDate parseDate(String date) { + return LocalDate.parse(date, STRING_TO_DATE_FORMAT); + } + + /** + * Checks if the start and end dates of this {@code BookingPeriod} overlaps with the other. + * Note: There is NO OVERLAPPING if this period's start date coincides with another's end date. + * @param other Other booking period to be compared to. + * @return True if there is any overlap, false otherwise. + */ + public boolean isOverlapping(BookingPeriod other) { + return this.equals(other) + || (startDate.isBefore(other.endDate) && other.startDate.isBefore(this.endDate)); + } + + /** + * Checks if this booking period is expired. + */ + public boolean isExpired() { + return endDate.isBefore(LocalDate.now()); + } + + /** + * Checks if this booking period is outdated. + */ + public boolean isOutdated() { + return startDate.isBefore(LocalDate.now()); + } + + /** + * Checks if this booking period is active now. + */ + public boolean isActive() { + LocalDate today = LocalDate.now(); + return includesDate(today); + } + + /** + * Checks if the given date is within this booking period's start and end dates, inclusively + */ + public boolean includesDate(LocalDate date) { + return date.compareTo(startDate) >= 0 + && date.compareTo(endDate) <= 0; + } + + /** + * Returns true if this booking period starts before the other + */ + public boolean startsBefore(BookingPeriod otherBookingPeriod) { + return startDate.isBefore(otherBookingPeriod.startDate); + } + + @Override + public String toString() { + String start = startDate.format(DATE_TO_STRING_FORMAT); + String end = endDate.format(DATE_TO_STRING_FORMAT); + return String.format("%s - %s", start, end); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof BookingPeriod // instanceof handles nulls + && startDate.equals(((BookingPeriod) other).startDate) // state check + && endDate.equals(((BookingPeriod) other).endDate)); + } + + @Override + public int hashCode() { + return Objects.hash(startDate, endDate); + } + + @Override + public int compareTo(BookingPeriod other) { + int result = startDate.compareTo(other.startDate); + if (result != 0) { + return result; + } + return endDate.compareTo(other.endDate); + } +} diff --git a/src/main/java/seedu/address/model/room/booking/Bookings.java b/src/main/java/seedu/address/model/room/booking/Bookings.java new file mode 100644 index 000000000000..e6258c287be3 --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/Bookings.java @@ -0,0 +1,230 @@ +package seedu.address.model.room.booking; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.function.Predicate; + +import seedu.address.model.room.booking.exceptions.BookingNotFoundException; +import seedu.address.model.room.booking.exceptions.NoBookingException; +import seedu.address.model.room.booking.exceptions.OverlappingBookingException; + +/** + * A sorted set of Bookings that maintains non-overlapping property between its elements and does not allow nulls. + * A Booking is considered non-overlapping by comparing using + * {@code Booking#canAcceptBooking(Booking)}. As such, adding and updating of + * Bookings uses Booking#canAcceptBooking(Booking) to ensure that the + * Booking being added or updated does not overlap any existing ones in Bookings. + * However, the removal of a Booking uses Booking#equals(Object) so + * as to ensure that the Booking with exactly the same fields will be removed. + * * + * Supports a minimal set of set operations. + * Guarantees immutability + * + * @see Booking#isOverlapping(Booking) + */ +public class Bookings { + + private final SortedSet sortedBookingsSet; + + /** + * Constructor for empty bookings set + */ + public Bookings() { + this.sortedBookingsSet = new TreeSet<>(); + } + + /** + * Constructor for creating a copy of a bookings + */ + public Bookings(SortedSet sortedBookingsSet) { + requireAllNonNull(sortedBookingsSet); + if (bookingsAreOverlapping(sortedBookingsSet)) { + throw new OverlappingBookingException(); + } + this.sortedBookingsSet = sortedBookingsSet; + } + + //=========== Getters ============================================================= + + public SortedSet getSortedBookingsSet() { + return Collections.unmodifiableSortedSet(sortedBookingsSet); + } + + /** + * Gets the first booking in the set + */ + public Booking getFirstBooking() { + if (sortedBookingsSet.isEmpty()) { + throw new NoBookingException(); + } + return sortedBookingsSet.first(); + } + + /** + * Returns an {@code Optional} of the first active booking of this room + * Note 1: We return an optional here and not throw exception because only GUI elements (RoomCard and + * RoomDetailedCard) use this method. It makes it difficult to read the GUI code if exceptions are handled there. + * Note 2: There can be more than one active booking at a time. + * For example, Booking A ends on 01/11/18 and Booking B starts on 01/11/18. This is allowed, to simulate a + * turnover of rooms in the same day. + */ + public Optional getFirstActiveBooking() { + return sortedBookingsSet.stream().filter(Booking::isActive).findFirst(); + } + + /** + * Return the first booking that matches the given predicate. + */ + public Booking getFirstBookingByPredicate(Predicate predicate) { + Optional optionalBooking = sortedBookingsSet.stream() + .filter(predicate) + .findFirst(); + if (!optionalBooking.isPresent()) { + throw new BookingNotFoundException(); + } + return optionalBooking.get(); + } + + //=========== Operations ============================================================= + + /** + * Adds a Booking to a copy of the set. + * The Booking must not already exist in the set. + */ + public Bookings add(Booking toAdd) { + requireNonNull(toAdd); + if (!canAcceptBooking(toAdd)) { + throw new OverlappingBookingException(); + } + SortedSet editedBookings = new TreeSet<>(this.sortedBookingsSet); + editedBookings.add(toAdd); + return new Bookings(editedBookings); + } + + /** + * Removes the equivalent Booking from a copy of the set. + * The Booking must exist in the set. + */ + public Bookings remove(Booking toRemove) { + requireNonNull(toRemove); + if (!sortedBookingsSet.contains(toRemove)) { + throw new BookingNotFoundException(); + } + SortedSet editedBookings = new TreeSet<>(this.sortedBookingsSet); + editedBookings.remove(toRemove); + return new Bookings(editedBookings); + } + + /** + * Replaces the Booking {@code target} in a copy of the set with {@code editedBooking}. + * {@code target} must exist in the set. + */ + public Bookings updateBooking(Booking target, Booking editedBooking) { + requireAllNonNull(target, editedBooking); + + if (!sortedBookingsSet.contains(target)) { + throw new BookingNotFoundException(); + } + + if (!target.isSameBooking(editedBooking) && !canAcceptIfReplaceBooking(target, editedBooking)) { + throw new OverlappingBookingException(); + } + + SortedSet editedBookings = new TreeSet<>(this.sortedBookingsSet); + editedBookings.remove(target); + editedBookings.add(editedBooking); + return new Bookings(editedBookings); + } + + //=========== Boolean checkers ============================================================= + + /** + * Returns true if the booking exists in this set of bookings + */ + public boolean contains(Booking booking) { + return sortedBookingsSet.contains(booking); + } + + /** + * Returns true if the given booking overlaps with any existing booking in the set + */ + private boolean canAcceptBooking(Booking toCheck) { + requireNonNull(toCheck); + return sortedBookingsSet.stream().noneMatch(toCheck::isOverlapping); + } + + /** + * Returns true if the given booking overlaps with any existing booking in the set, excluding the one it replaces + */ + private boolean canAcceptIfReplaceBooking(Booking toReplace, Booking toCheck) { + requireAllNonNull(toReplace, toCheck); + return sortedBookingsSet.stream().noneMatch( + booking -> !booking.equals(toReplace) && booking.isOverlapping(toCheck)); + } + + /** + * Returns true if {@code Bookings} contains at least one overlapping Booking. + */ + private static boolean bookingsAreOverlapping(Set bookings) { + return bookings.stream().anyMatch(b1 -> + bookings.stream().anyMatch(b2 -> !b1.equals(b2) && b1.isOverlapping(b2))); + } + + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Bookings // instanceof handles nulls + && sortedBookingsSet.equals(((Bookings) other).sortedBookingsSet)); + } + + @Override + public int hashCode() { + return sortedBookingsSet.hashCode(); + } + + /** + * Returns the short description of the active booking + */ + public String toStringActiveBookingShortDescription() { + return getFirstActiveBooking().map(Booking::toStringShortDescription).orElse(""); + } + + /** + * Returns the full description of the active booking + */ + public String toStringActiveBooking() { + return getFirstActiveBooking().map(Booking::toString).orElse(""); + } + + /** + * Returns the full description of all non-active bookings + */ + public String toStringAllOtherBookings() { + final StringBuilder builder = new StringBuilder(); + Bookings allOtherBookings = this; + Optional optionalActiveBooking = getFirstActiveBooking(); + if (optionalActiveBooking.isPresent()) { + allOtherBookings = this.remove(optionalActiveBooking.get()); + } + builder.append(allOtherBookings); + return builder.toString(); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + int index = 1; + for (Booking booking : sortedBookingsSet) { + builder.append(index).append(". ").append(booking); + index++; + } + return builder.toString(); + } +} diff --git a/src/main/java/seedu/address/model/room/booking/exceptions/BookingAlreadyCheckedInException.java b/src/main/java/seedu/address/model/room/booking/exceptions/BookingAlreadyCheckedInException.java new file mode 100644 index 000000000000..1aacef680358 --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/exceptions/BookingAlreadyCheckedInException.java @@ -0,0 +1,10 @@ +package seedu.address.model.room.booking.exceptions; + +/** + * Signals that the operation is attempting to check-in a guest into a booking that is already checked-in + */ +public class BookingAlreadyCheckedInException extends RuntimeException { + public BookingAlreadyCheckedInException() { + super("Cannot check-in a booking that is already checked-in."); + } +} diff --git a/src/main/java/seedu/address/model/room/booking/exceptions/BookingNotFoundException.java b/src/main/java/seedu/address/model/room/booking/exceptions/BookingNotFoundException.java new file mode 100644 index 000000000000..ae4606b3f278 --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/exceptions/BookingNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.room.booking.exceptions; + +/** + * Signals that the operation is unable to find the specified booking date. + */ +public class BookingNotFoundException extends RuntimeException { + public BookingNotFoundException() { + super("No such booking found."); + } +} diff --git a/src/main/java/seedu/address/model/room/booking/exceptions/ExpiredBookingException.java b/src/main/java/seedu/address/model/room/booking/exceptions/ExpiredBookingException.java new file mode 100644 index 000000000000..11cde06c8f4c --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/exceptions/ExpiredBookingException.java @@ -0,0 +1,10 @@ +package seedu.address.model.room.booking.exceptions; + +/** + * Signals that there was an attempt to do an invalid operation on an expired booking. + */ +public class ExpiredBookingException extends RuntimeException { + public ExpiredBookingException() { + super("Invalid operation, as the booking has already expired."); + } +} diff --git a/src/main/java/seedu/address/model/room/booking/exceptions/InactiveBookingCheckInException.java b/src/main/java/seedu/address/model/room/booking/exceptions/InactiveBookingCheckInException.java new file mode 100644 index 000000000000..a0eb98c20e84 --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/exceptions/InactiveBookingCheckInException.java @@ -0,0 +1,10 @@ +package seedu.address.model.room.booking.exceptions; + +/** + * Signals that there was an attempt to check-in an inactive booking. + */ +public class InactiveBookingCheckInException extends RuntimeException { + public InactiveBookingCheckInException() { + super("Cannot check-in a booking that is not active."); + } +} diff --git a/src/main/java/seedu/address/model/room/booking/exceptions/NewBookingStartsBeforeOldBookingCheckedIn.java b/src/main/java/seedu/address/model/room/booking/exceptions/NewBookingStartsBeforeOldBookingCheckedIn.java new file mode 100644 index 000000000000..3138084b9574 --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/exceptions/NewBookingStartsBeforeOldBookingCheckedIn.java @@ -0,0 +1,13 @@ +package seedu.address.model.room.booking.exceptions; + +/** + * Signals that the reassignment operations involves: + * 1) New booking starts before old booking + * 2) Old booking is already checked-in + */ +public class NewBookingStartsBeforeOldBookingCheckedIn extends RuntimeException { + public NewBookingStartsBeforeOldBookingCheckedIn() { + super("Cannot reassign booking to new room, because new booking starts before old booking" + + "and old room is currently checked in!"); + } +} diff --git a/src/main/java/seedu/address/model/room/booking/exceptions/NoBookingException.java b/src/main/java/seedu/address/model/room/booking/exceptions/NoBookingException.java new file mode 100644 index 000000000000..294b7bf614ec --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/exceptions/NoBookingException.java @@ -0,0 +1,10 @@ +package seedu.address.model.room.booking.exceptions; + +/** + * Signals that the operation was unable to find any bookings. + */ +public class NoBookingException extends RuntimeException { + public NoBookingException() { + super("No booking found."); + } +} diff --git a/src/main/java/seedu/address/model/room/booking/exceptions/OldBookingStartsBeforeNewBookingCheckedIn.java b/src/main/java/seedu/address/model/room/booking/exceptions/OldBookingStartsBeforeNewBookingCheckedIn.java new file mode 100644 index 000000000000..0d341118cd49 --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/exceptions/OldBookingStartsBeforeNewBookingCheckedIn.java @@ -0,0 +1,13 @@ +package seedu.address.model.room.booking.exceptions; + +/** + * Signals that the reassignment operations involves: + * 1) Old booking starts before new booking + * 2) New booking is already checked-in + */ +public class OldBookingStartsBeforeNewBookingCheckedIn extends RuntimeException { + public OldBookingStartsBeforeNewBookingCheckedIn() { + super("Cannot reassign booking to new room, because old booking starts before new booking" + + "and new room is currently checked in!"); + } +} diff --git a/src/main/java/seedu/address/model/room/booking/exceptions/OverlappingBookingException.java b/src/main/java/seedu/address/model/room/booking/exceptions/OverlappingBookingException.java new file mode 100644 index 000000000000..59f122ce54a4 --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/exceptions/OverlappingBookingException.java @@ -0,0 +1,10 @@ +package seedu.address.model.room.booking.exceptions; + +/** + * Signals that the operation will result in overlapping Bookings + */ +public class OverlappingBookingException extends RuntimeException { + public OverlappingBookingException() { + super("Operation would result in overlapping booking dates."); + } +} diff --git a/src/main/java/seedu/address/model/room/booking/exceptions/RoomNotCheckedInException.java b/src/main/java/seedu/address/model/room/booking/exceptions/RoomNotCheckedInException.java new file mode 100644 index 000000000000..5759cfa98fbb --- /dev/null +++ b/src/main/java/seedu/address/model/room/booking/exceptions/RoomNotCheckedInException.java @@ -0,0 +1,10 @@ +package seedu.address.model.room.booking.exceptions; + +/** + * Exception to indicate that the room is not checked in. + */ +public class RoomNotCheckedInException extends RuntimeException { + public RoomNotCheckedInException() { + super("There are no guests checked in to the room."); + } +} diff --git a/src/main/java/seedu/address/model/room/exceptions/DuplicateRoomException.java b/src/main/java/seedu/address/model/room/exceptions/DuplicateRoomException.java new file mode 100644 index 000000000000..6d2383919b27 --- /dev/null +++ b/src/main/java/seedu/address/model/room/exceptions/DuplicateRoomException.java @@ -0,0 +1,10 @@ +package seedu.address.model.room.exceptions; + +/** + * Signals that the operation will result in duplicate Rooms + */ +public class DuplicateRoomException extends RuntimeException { + public DuplicateRoomException() { + super("Operation would result in duplicate rooms."); + } +} diff --git a/src/main/java/seedu/address/model/room/exceptions/OriginalRoomReassignException.java b/src/main/java/seedu/address/model/room/exceptions/OriginalRoomReassignException.java new file mode 100644 index 000000000000..de7b1c44cc89 --- /dev/null +++ b/src/main/java/seedu/address/model/room/exceptions/OriginalRoomReassignException.java @@ -0,0 +1,10 @@ +package seedu.address.model.room.exceptions; + +/** + * Signals that the operation is attempting to reassign a booking back to the original room + */ +public class OriginalRoomReassignException extends RuntimeException { + public OriginalRoomReassignException() { + super("Cannot reassign booking to the original room"); + } +} diff --git a/src/main/java/seedu/address/model/room/exceptions/RoomMissingException.java b/src/main/java/seedu/address/model/room/exceptions/RoomMissingException.java new file mode 100644 index 000000000000..21e36b759bc1 --- /dev/null +++ b/src/main/java/seedu/address/model/room/exceptions/RoomMissingException.java @@ -0,0 +1,10 @@ +package seedu.address.model.room.exceptions; + +/** + * Signals that the operation will result in missing room(s) + */ +public class RoomMissingException extends RuntimeException { + public RoomMissingException() { + super("Operation would result in missing rooms."); + } +} diff --git a/src/main/java/seedu/address/model/room/exceptions/RoomNotFoundException.java b/src/main/java/seedu/address/model/room/exceptions/RoomNotFoundException.java new file mode 100644 index 000000000000..f1a38c808f11 --- /dev/null +++ b/src/main/java/seedu/address/model/room/exceptions/RoomNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.room.exceptions; + +/** + * Signals that the operation is unable to find the specified room. + */ +public class RoomNotFoundException extends RuntimeException { + public RoomNotFoundException() { + super("No such room found."); + } +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index 8cdff2773ac9..d84c69f3136b 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -4,7 +4,7 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Tag in the address book. + * Represents a Tag in Concierge. * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ public class Tag { diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facfa..a147728075ce 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,50 +1,140 @@ package seedu.address.model.util; +import static java.util.Objects.requireNonNull; + +import java.time.LocalDate; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; 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 javafx.util.Pair; +import seedu.address.model.Concierge; +import seedu.address.model.ReadOnlyConcierge; +import seedu.address.model.expenses.Expense; +import seedu.address.model.expenses.ExpenseType; +import seedu.address.model.expenses.Money; +import seedu.address.model.guest.Email; +import seedu.address.model.guest.Guest; +import seedu.address.model.guest.Name; +import seedu.address.model.guest.Phone; +import seedu.address.model.login.PasswordHashList; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.UniqueRoomList; +import seedu.address.model.room.booking.Booking; +import seedu.address.model.room.booking.BookingPeriod; import seedu.address.model.tag.Tag; /** - * Contains utility methods for populating {@code AddressBook} with sample data. + * Contains utility methods for populating {@code Concierge} 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 Guest[] getSampleGuests() { + return new Guest[] { + new Guest(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + getTagSet("friends")), + new Guest(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), + getTagSet("colleagues", "friends")), + new Guest(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), + getTagSet("neighbours")), + new Guest(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), + getTagSet("family")) + }; + } + + /** + * Returns a room list initialized with the maximum number of rooms as set in RoomNumber class + */ + public static List getSampleRooms() { + return new UniqueRoomList().asUnmodifiableObservableList(); + } + + public static ExpenseType[] getSampleExpenseTypes() { + return new ExpenseType[] { + new ExpenseType("RS01", "Room service: Red wine", new Money(50, 0)), + new ExpenseType("RS02", "Room service: Beef steak", new Money(70, 0)), + new ExpenseType("RS03", "Room service: Thai massage", new Money(100, 0)), + new ExpenseType("SP01", "Swimming pool: Entry", new Money(5, 0)), + new ExpenseType("MB01", "Minibar: Coca cola", new Money(3, 0)), + new ExpenseType("MB02", "Minibar: Sprite", new Money(3, 0)), + new ExpenseType("MB03", "Minibar: Tiger beer", new Money(6, 0)), + new ExpenseType("MB04", "Minibar: Mineral water", new Money(3, 0)), + new ExpenseType("XX01", "Adjustment: Discount", new Money(0, 0)), + new ExpenseType("XX02", "Adjustment: Typo", new Money(0, 0)) }; } - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + public static Map getSampleMenuMap() { + HashMap sampleMenuMap = new HashMap<>(); + for (ExpenseType expenseType : getSampleExpenseTypes()) { + sampleMenuMap.put(expenseType.getItemNumber(), expenseType); } + return sampleMenuMap; + } + + public static ReadOnlyConcierge getEmptyConcierge() { + Concierge sampleAb = new Concierge(); + sampleAb.setRooms(getSampleRooms()); + sampleAb.setMenu(getSampleMenuMap()); + return sampleAb; + } + + public static ReadOnlyConcierge getSampleConcierge() { + Concierge sampleAb = new Concierge(); + for (Guest sampleGuest : getSampleGuests()) { + sampleAb.addGuest(sampleGuest); + } + + sampleAb.setRooms(getSampleRooms()); + // add bookings and expenses to room 001 + RoomNumber roomNumberToEdit = new RoomNumber("001"); + sampleAb.addBooking(roomNumberToEdit, + new Booking(getSampleGuests()[0], new BookingPeriod( + LocalDate.now().format(BookingPeriod.DATE_TO_STRING_FORMAT), + LocalDate.now().plusDays(1).format(BookingPeriod.DATE_TO_STRING_FORMAT)))); + sampleAb.checkInRoom(roomNumberToEdit); + sampleAb.addExpense(roomNumberToEdit, new Expense(getSampleExpenseTypes()[0])); + + // add bookings and expenses to room 002 + roomNumberToEdit = new RoomNumber("002"); + sampleAb.addBooking(roomNumberToEdit, + new Booking(getSampleGuests()[1], new BookingPeriod( + LocalDate.now().format(BookingPeriod.DATE_TO_STRING_FORMAT), + LocalDate.now().plusDays(2).format(BookingPeriod.DATE_TO_STRING_FORMAT)))); + + // add bookings and expenses to room 003 + roomNumberToEdit = new RoomNumber("003"); + sampleAb.addBooking(roomNumberToEdit, + new Booking(getSampleGuests()[2], new BookingPeriod( + LocalDate.now().plusDays(1).format(BookingPeriod.DATE_TO_STRING_FORMAT), + LocalDate.now().plusDays(2).format(BookingPeriod.DATE_TO_STRING_FORMAT)))); + + // add bookings and expenses to room 004 + roomNumberToEdit = new RoomNumber("004"); + sampleAb.addBooking(roomNumberToEdit, + new Booking(getSampleGuests()[3], new BookingPeriod( + LocalDate.now().minusDays(1).format(BookingPeriod.DATE_TO_STRING_FORMAT), + LocalDate.now().plusDays(2).format(BookingPeriod.DATE_TO_STRING_FORMAT)))); + + // add bookings and expenses to room 005 + roomNumberToEdit = new RoomNumber("005"); + sampleAb.addBooking(roomNumberToEdit, + new Booking(getSampleGuests()[3], new BookingPeriod( + LocalDate.now().minusDays(1).format(BookingPeriod.DATE_TO_STRING_FORMAT), + LocalDate.now().format(BookingPeriod.DATE_TO_STRING_FORMAT)))); + + // add bookings and expenses to room 006 + roomNumberToEdit = new RoomNumber("006"); + sampleAb.addBooking(roomNumberToEdit, + new Booking(getSampleGuests()[3], new BookingPeriod( + LocalDate.now().minusWeeks(1).format(BookingPeriod.DATE_TO_STRING_FORMAT), + LocalDate.now().minusDays(2).format(BookingPeriod.DATE_TO_STRING_FORMAT)))); + + sampleAb.setMenu(getSampleMenuMap()); return sampleAb; } @@ -57,4 +147,29 @@ public static Set getTagSet(String... strings) { .collect(Collectors.toSet()); } + /** + * @return An sample password hash list with the following login + * credentials. + * Username: admin, Password: passw0rd + */ + public static PasswordHashList getDefaultPasswordHashList() { + return getPasswordHashList( + new Pair<>("admin", "passw0rd")); + } + + /** + * Returns a PasswordHashList given pairs of keys and values. + */ + public static PasswordHashList getPasswordHashList(Pair... pairs) { + requireNonNull(pairs); + + PasswordHashList passwordHashList = new PasswordHashList(); + + for (Pair p: pairs) { + passwordHashList = passwordHashList.addEntry( + (String) p.getKey(), (String) p.getValue()); + } + + return passwordHashList; + } } 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/ConciergeStorage.java b/src/main/java/seedu/address/storage/ConciergeStorage.java new file mode 100644 index 000000000000..f19d062e97d7 --- /dev/null +++ b/src/main/java/seedu/address/storage/ConciergeStorage.java @@ -0,0 +1,45 @@ +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.ReadOnlyConcierge; + +/** + * Represents a storage for {@link seedu.address.model.Concierge}. + */ +public interface ConciergeStorage { + + /** + * Returns the file path of the data file. + */ + Path getConciergeFilePath(); + + /** + * Returns Concierge data as a {@link ReadOnlyConcierge}. + * 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 readConcierge() throws DataConversionException, IOException; + + /** + * @see #getConciergeFilePath() + */ + Optional readConcierge(Path filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyConcierge} to the storage. + * @param concierge cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveConcierge(ReadOnlyConcierge concierge) throws IOException; + + /** + * @see #saveConcierge(ReadOnlyConcierge) + */ + void saveConcierge(ReadOnlyConcierge concierge, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/JsonPasswordsStorage.java b/src/main/java/seedu/address/storage/JsonPasswordsStorage.java new file mode 100644 index 000000000000..237beb09e0f2 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonPasswordsStorage.java @@ -0,0 +1,37 @@ +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.commons.util.JsonUtil; +import seedu.address.model.login.PasswordHashList; + +/** + * A class to access the passwords stored in the hard disk as a json file + */ +public class JsonPasswordsStorage implements PasswordsStorage { + + private Path filePath; + + public JsonPasswordsStorage(Path filePath) { + this.filePath = filePath; + } + + @Override + public Path getPasswordsFilePath() { + return filePath; + } + + @Override + public Optional readPasswordRef() throws DataConversionException { + return JsonUtil.readJsonFile(filePath, PasswordHashList.class); + } + + @Override + public void savePasswordRef(PasswordHashList passwordHashList) throws IOException { + JsonUtil.saveJsonFile(passwordHashList, filePath); + } + +} diff --git a/src/main/java/seedu/address/storage/PasswordsStorage.java b/src/main/java/seedu/address/storage/PasswordsStorage.java new file mode 100644 index 000000000000..5fa0e0bd363d --- /dev/null +++ b/src/main/java/seedu/address/storage/PasswordsStorage.java @@ -0,0 +1,35 @@ +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.login.PasswordHashList; + +/** + * Represents a storage for user passwords + */ +public interface PasswordsStorage { + + /** + * Returns the file path of the passwords data file. + */ + Path getPasswordsFilePath(); + + /** + * Returns password list from storage + * 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 readPasswordRef() throws DataConversionException, IOException; + + /** + * Saves the given {@link seedu.address.model.login.PasswordHashList} to the storage. + * @param passwordRef cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void savePasswordRef(PasswordHashList passwordRef) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index 28791127999b..218fff820f61 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -4,16 +4,17 @@ import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.ConciergeChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyConcierge; import seedu.address.model.UserPrefs; +import seedu.address.model.login.PasswordHashList; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends ConciergeStorage, UserPrefsStorage, PasswordsStorage { @Override Optional readUserPrefs() throws DataConversionException, IOException; @@ -22,18 +23,27 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { void saveUserPrefs(UserPrefs userPrefs) throws IOException; @Override - Path getAddressBookFilePath(); + Path getConciergeFilePath(); @Override - Optional readAddressBook() throws DataConversionException, IOException; + Optional readConcierge() throws DataConversionException, IOException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveConcierge(ReadOnlyConcierge concierge) throws IOException; + + @Override + Path getPasswordsFilePath(); + + @Override + Optional readPasswordRef() throws DataConversionException, IOException; + + @Override + void savePasswordRef(PasswordHashList passwordRef) throws IOException; /** - * Saves the current version of the Address Book to the hard disk. + * Saves the current version of Concierge 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 handleConciergeChangedEvent(ConciergeChangedEvent abce); } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index b0df908a76a7..00649b41c408 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -9,29 +9,50 @@ 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.ConciergeChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyConcierge; import seedu.address.model.UserPrefs; +import seedu.address.model.login.PasswordHashList; /** - * Manages storage of AddressBook data in local storage. + * Manages storage of Concierge data in local storage. */ public class StorageManager extends ComponentManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; + private ConciergeStorage conciergeStorage; private UserPrefsStorage userPrefsStorage; + private PasswordsStorage passwordsStorage; - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(ConciergeStorage conciergeStorage, + UserPrefsStorage userPrefsStorage, + PasswordsStorage passwordsStorage) { super(); - this.addressBookStorage = addressBookStorage; + this.conciergeStorage = conciergeStorage; this.userPrefsStorage = userPrefsStorage; + this.passwordsStorage = passwordsStorage; } + // ================ SignIn / Password methods ============================== - // ================ UserPrefs methods ============================== + @Override + public Path getPasswordsFilePath() { + return passwordsStorage.getPasswordsFilePath(); + } + + @Override + public Optional readPasswordRef() throws DataConversionException, IOException { + return passwordsStorage.readPasswordRef(); + } + + @Override + public void savePasswordRef(PasswordHashList passwordHashList) throws IOException { + passwordsStorage.savePasswordRef(passwordHashList); + } + + // ================ UserPrefs methods ====================================== @Override public Path getUserPrefsFilePath() { @@ -49,42 +70,42 @@ public void saveUserPrefs(UserPrefs userPrefs) throws IOException { } - // ================ AddressBook methods ============================== + // ================ Concierge methods ============================== @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); + public Path getConciergeFilePath() { + return conciergeStorage.getConciergeFilePath(); } @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); + public Optional readConcierge() throws DataConversionException, IOException { + return readConcierge(conciergeStorage.getConciergeFilePath()); } @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { + public Optional readConcierge(Path filePath) throws DataConversionException, IOException { logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); + return conciergeStorage.readConcierge(filePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); + public void saveConcierge(ReadOnlyConcierge concierge) throws IOException { + saveConcierge(concierge, conciergeStorage.getConciergeFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveConcierge(ReadOnlyConcierge concierge, Path filePath) throws IOException { logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); + conciergeStorage.saveConcierge(concierge, filePath); } @Override @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { + public void handleConciergeChangedEvent(ConciergeChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); try { - saveAddressBook(event.data); + saveConcierge(event.data); } 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..a0e52abcd27a 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/address/storage/UserPrefsStorage.java @@ -19,7 +19,7 @@ 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. */ diff --git a/src/main/java/seedu/address/storage/XmlAdaptedBooking.java b/src/main/java/seedu/address/storage/XmlAdaptedBooking.java new file mode 100644 index 000000000000..e55ba3cde018 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedBooking.java @@ -0,0 +1,91 @@ +package seedu.address.storage; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.guest.Guest; +import seedu.address.model.room.Capacity; +import seedu.address.model.room.booking.Booking; +import seedu.address.model.room.booking.BookingPeriod; + +/** + * JAXB-friendly version of the Booking. + */ +public class XmlAdaptedBooking { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Booking's %s field is missing!"; + + @XmlElement(required = true) + private XmlAdaptedGuest guest; + @XmlElement(required = true) + private XmlAdaptedBookingPeriod bookingPeriod; + @XmlElement(required = true) + private Boolean isCheckedIn; + + /** + * Constructs an XmlAdaptedBooking. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedBooking() {} + + /** + * Constructor FOR TESTING in XmlAdaptedBookingTest + */ + public XmlAdaptedBooking(Guest guest, BookingPeriod bookingPeriod, Boolean isCheckedIn) { + this.guest = new XmlAdaptedGuest(guest); + this.bookingPeriod = new XmlAdaptedBookingPeriod(bookingPeriod); + this.isCheckedIn = isCheckedIn; + } + + /** + * Converts a given booking into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedBooking + */ + public XmlAdaptedBooking(Booking source) { + guest = new XmlAdaptedGuest(source.getGuest()); + bookingPeriod = new XmlAdaptedBookingPeriod(source.getBookingPeriod()); + isCheckedIn = source.getIsCheckedIn(); + } + + /** + * Converts this jaxb-friendly adapted booking object into the model's booking object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted booking + */ + public Booking toModelType() throws IllegalValueException { + + if (guest == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Guest.class.getSimpleName())); + } + if (bookingPeriod == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Capacity.class.getSimpleName())); + } + if (isCheckedIn == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Boolean.class.getSimpleName())); + } + + return new Booking(guest.toModelType(), bookingPeriod.toModelType(), isCheckedIn); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedBooking)) { + return false; + } + + XmlAdaptedBooking otherbooking = (XmlAdaptedBooking) other; + return Objects.equals(guest, otherbooking.guest) + && Objects.equals(bookingPeriod, otherbooking.bookingPeriod) + && Objects.equals(isCheckedIn, otherbooking.isCheckedIn); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedBookingPeriod.java b/src/main/java/seedu/address/storage/XmlAdaptedBookingPeriod.java new file mode 100644 index 000000000000..8161a1c7d52a --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedBookingPeriod.java @@ -0,0 +1,79 @@ +package seedu.address.storage; + +import java.time.LocalDate; +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.room.booking.BookingPeriod; + +/** + * JAXB-friendly version of the BookingPeriod. + */ +public class XmlAdaptedBookingPeriod { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "BookingPeriod's %s field is missing!"; + + @XmlElement(required = true) + private String startDate; + @XmlElement(required = true) + private String endDate; + + /** + * Constructs an XmlAdaptedBookingPeriod. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedBookingPeriod() {} + + /** + * Constructor FOR TESTING in XmlAdaptedBookingPeriodTest + */ + public XmlAdaptedBookingPeriod(String startDate, String endDate) { + this.startDate = startDate; + this.endDate = endDate; + } + + /** + * Converts a given booking into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedBookingPeriod + */ + public XmlAdaptedBookingPeriod(BookingPeriod source) { + startDate = source.getStartDateAsFormattedString(); + endDate = source.getEndDateAsFormattedString(); + } + + /** + * Converts this jaxb-friendly adapted booking object into the model's booking object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted booking + */ + public BookingPeriod toModelType() throws IllegalValueException { + + if (startDate == null || endDate == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, LocalDate.class.getSimpleName())); + } + if (!BookingPeriod.isValidBookingPeriod(startDate, endDate)) { + throw new IllegalValueException(BookingPeriod.MESSAGE_BOOKING_PERIOD_CONSTRAINTS); + } + + return new BookingPeriod(startDate, endDate); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedBookingPeriod)) { + return false; + } + + XmlAdaptedBookingPeriod otherbookingPeriod = (XmlAdaptedBookingPeriod) other; + return Objects.equals(startDate, otherbookingPeriod.startDate) + && Objects.equals(endDate, otherbookingPeriod.endDate); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedExpense.java b/src/main/java/seedu/address/storage/XmlAdaptedExpense.java new file mode 100644 index 000000000000..455e39b12e96 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedExpense.java @@ -0,0 +1,78 @@ +package seedu.address.storage; + +import java.time.LocalDateTime; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.Menu; +import seedu.address.model.expenses.Expense; +import seedu.address.model.expenses.ExpenseType; +import seedu.address.model.expenses.Money; + +/** + * JAXB-friendly adapted version of Expense. + */ +public class XmlAdaptedExpense { + + public static final String EXPENSE_TYPE_UNKNOWN_NAME = "Unknown"; + public static final Money EXPENSE_TYPE_UNKNOWN_COST = new Money(0, 0); + public static final String MESSAGE_INVALID_COST = "The given cost is not in the correct format."; + + @XmlElement (required = true) + private String item; + @XmlElement (required = true) + private String cost; + @XmlElement (required = true) + private String datetime; + + /** + * Constructs an XmlAdaptedExpense. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedExpense() {} + + /** + * Constructs an XmlAdaptedExpense with the given fields. + * + * @param item The menu number of the item. + * @param cost The cost incurred in this expense. + * @param datetime The date and time of this transaction. + */ + public XmlAdaptedExpense(String item, String cost, String datetime) { + this.item = item; + this.cost = cost; + this.datetime = datetime; + } + + /** + * Converts an Expense object into JAXB-friendly form. + * + * @param source The Expense object to be converted. + */ + public XmlAdaptedExpense(Expense source) { + this.item = source.getExpenseType().getItemNumber(); + this.cost = source.getCost().toString(); + this.datetime = source.getDateTimeString(); + } + + /** + * Converts this object into the proper Expense object. + * @return The Expense representation of this object. + */ + public Expense toModelType(Menu menu) throws IllegalValueException { + ExpenseType expenseType; + if (!menu.isValidMenuNumber(item)) { + expenseType = new ExpenseType(item, EXPENSE_TYPE_UNKNOWN_NAME, EXPENSE_TYPE_UNKNOWN_COST); + } else { + expenseType = menu.getExpenseType(item); + } + if (!Money.isValidMoneyFormat(cost)) { + throw new IllegalValueException(MESSAGE_INVALID_COST); + } + Money moneyCost = new Money(cost); + LocalDateTime localDateTime = LocalDateTime.parse(datetime, Expense.DATETIME_FORMAT); + return new Expense(expenseType, moneyCost, localDateTime); + } + +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedExpenseType.java b/src/main/java/seedu/address/storage/XmlAdaptedExpenseType.java new file mode 100644 index 000000000000..cd3aa8817534 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedExpenseType.java @@ -0,0 +1,102 @@ +package seedu.address.storage; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.expenses.ExpenseType; +import seedu.address.model.expenses.Money; + +/** + * JAXB-friendly adapted version of ExpenseType. + */ +public class XmlAdaptedExpenseType { + + public static final String MESSAGE_NUMBER_MISSING = "ExpenseType's number field is missing!"; + public static final String MESSAGE_NAME_MISSING = "ExpenseType's name field is missing!"; + public static final String MESSAGE_COST_MISSING = "ExpenseType's cost field is missing!"; + + @XmlElement (required = true) + private String itemNumber; + @XmlElement (required = true) + private String itemName; + @XmlElement (required = true) + private String itemCost; + + /** + * Constructs an XmlAdaptedExpenseType. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedExpenseType() {} + + /** + * Constructs an XmlAdaptedExpenseType with the given fields. + * @param itemNumber The menu number of the item. + * @param itemName The cost incurred in this expense. + * @param itemCost The date and time of this transaction. + */ + public XmlAdaptedExpenseType(String itemNumber, String itemName, String itemCost) { + this.itemNumber = itemNumber; + this.itemName = itemName; + this.itemCost = itemCost; + } + + /** + * Converts an ExpenseType object into JAXB-friendly form. + * + * @param source The ExpenseType object to be converted. + */ + public XmlAdaptedExpenseType(ExpenseType source) { + this.itemNumber = source.getItemNumber(); + this.itemName = source.getItemName(); + this.itemCost = source.getItemCost().toString(); + } + + /** + * Converts this object into the proper ExpenseType object. + * @return The ExpenseType representation of this object. + */ + public ExpenseType toModelType() throws IllegalValueException { + if (itemNumber == null) { + throw new IllegalValueException(MESSAGE_NUMBER_MISSING); + } + if (itemName == null) { + throw new IllegalValueException(MESSAGE_NAME_MISSING); + } + if (itemCost == null) { + throw new IllegalValueException(MESSAGE_COST_MISSING); + } + if (!Money.isValidMoneyFormat(itemCost)) { + throw new IllegalValueException(XmlAdaptedExpense.MESSAGE_INVALID_COST); + } + try { + return new ExpenseType(itemNumber, itemName, new Money(itemCost)); + } catch (IllegalArgumentException iae) { + throw new IllegalValueException(iae.getMessage()); + } + } + + public String getItemNumber() { + return itemNumber; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof XmlAdaptedExpenseType)) { + return false; + } + XmlAdaptedExpenseType otherExpenseType = (XmlAdaptedExpenseType) other; + return itemNumber.equals(otherExpenseType.itemNumber) + && itemName.equals(otherExpenseType.itemName) + && itemCost.equals(otherExpenseType.itemCost); + } + + @Override + public int hashCode() { + return Objects.hash(itemNumber, itemName, itemCost); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedGuest.java similarity index 54% rename from src/main/java/seedu/address/storage/XmlAdaptedPerson.java rename to src/main/java/seedu/address/storage/XmlAdaptedGuest.java index c03785e5700f..e26298b9941e 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedGuest.java @@ -8,21 +8,23 @@ 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.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.guest.Email; +import seedu.address.model.guest.Guest; +import seedu.address.model.guest.Name; +import seedu.address.model.guest.Phone; import seedu.address.model.tag.Tag; /** - * JAXB-friendly version of the Person. + * JAXB-friendly version of the Guest. + * Note: Tagged as XmlRootElement for XML tests to be able to write XML files for just this class */ -public class XmlAdaptedPerson { +@XmlRootElement(name = "guest") +public class XmlAdaptedGuest { - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Guest's %s field is missing!"; @XmlElement(required = true) private String name; @@ -30,55 +32,51 @@ public class XmlAdaptedPerson { private String phone; @XmlElement(required = true) private String email; - @XmlElement(required = true) - private String address; @XmlElement private List tagged = new ArrayList<>(); /** - * Constructs an XmlAdaptedPerson. + * Constructs an XmlAdaptedGuest. * This is the no-arg constructor that is required by JAXB. */ - public XmlAdaptedPerson() {} + public XmlAdaptedGuest() {} /** - * Constructs an {@code XmlAdaptedPerson} with the given person details. + * Constructs an {@code XmlAdaptedGuest} with the given guest details. */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List tagged) { + public XmlAdaptedGuest(String name, String phone, String email, 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. + * Converts a given Guest into this class for JAXB use. * - * @param source future changes to this will not affect the created XmlAdaptedPerson + * @param source future changes to this will not affect the created XmlAdaptedGuest */ - public XmlAdaptedPerson(Person source) { + public XmlAdaptedGuest(Guest 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. + * Converts this jaxb-friendly adapted guest object into the model's Guest 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 guest */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); + public Guest toModelType() throws IllegalValueException { + final List guestTags = new ArrayList<>(); for (XmlAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); + guestTags.add(tag.toModelType()); } if (name == null) { @@ -105,16 +103,8 @@ public Person toModelType() throws IllegalValueException { } 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); + final Set modelTags = new HashSet<>(guestTags); + return new Guest(modelName, modelPhone, modelEmail, modelTags); } @Override @@ -123,15 +113,14 @@ public boolean equals(Object other) { return true; } - if (!(other instanceof XmlAdaptedPerson)) { + if (!(other instanceof XmlAdaptedGuest)) { 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); + XmlAdaptedGuest otherGuest = (XmlAdaptedGuest) other; + return Objects.equals(name, otherGuest.name) + && Objects.equals(phone, otherGuest.phone) + && Objects.equals(email, otherGuest.email) + && tagged.equals(otherGuest.tagged); } } diff --git a/src/main/java/seedu/address/storage/XmlAdaptedRoom.java b/src/main/java/seedu/address/storage/XmlAdaptedRoom.java new file mode 100644 index 000000000000..5d8c8588dc4b --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedRoom.java @@ -0,0 +1,168 @@ +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 javax.xml.bind.annotation.XmlRootElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.Menu; +import seedu.address.model.expenses.Expense; +import seedu.address.model.expenses.Expenses; +import seedu.address.model.room.Capacity; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.booking.Booking; +import seedu.address.model.room.booking.Bookings; +import seedu.address.model.room.booking.exceptions.NoBookingException; +import seedu.address.model.room.booking.exceptions.OverlappingBookingException; +import seedu.address.model.tag.Tag; + +/** + * JAXB-friendly version of the Room. + * Note: Tagged as XmlRootElement for XML tests to be able to write XML files for just this class + */ +@XmlRootElement(name = "room") +public class XmlAdaptedRoom { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Room's %s field is missing!"; + public static final String MESSAGE_OVERLAPPING_BOOKING = "Room contains overlapping bookings!"; + public static final String MESSAGE_NO_BOOKING_TO_ADD_EXPENSES = "Room has no booking to add expenses!"; + public static final String MESSAGE_NOT_CHECKED_IN_TO_ADD_EXPENSES = "Room is not checked in to add expenses!"; + + @XmlElement(required = true) + private String roomNumber; + @XmlElement(required = true) + private Capacity capacity; + + @XmlElement + private List bookings = new ArrayList<>(); + @XmlElement + private List expenses = new ArrayList<>(); + @XmlElement + private List tagged = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedRoom. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedRoom() {} + + /** + * Constructor FOR TESTING in XmlAdaptedRoomTest + */ + public XmlAdaptedRoom(String roomNumber, Capacity capacity, List bookings, + List expenses, List tagged) { + this.roomNumber = roomNumber; + this.capacity = capacity; + this.bookings = bookings; + this.expenses = expenses; + this.tagged = tagged; + } + + /** + * Converts a given room into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedRoom + */ + public XmlAdaptedRoom(Room source) { + roomNumber = source.getRoomNumber().toString(); + capacity = source.getCapacity(); + expenses.addAll(source.getExpenses().getExpensesList().stream().map(XmlAdaptedExpense::new) + .collect(Collectors.toList())); + bookings.addAll(source.getBookings().getSortedBookingsSet().stream().map(XmlAdaptedBooking::new) + .collect(Collectors.toList())); + tagged = source.getTags().stream().map(XmlAdaptedTag::new).collect(Collectors.toList()); + } + + /** + * Converts this jaxb-friendly adapted room object into the model's room object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted room + */ + public Room toModelType(Menu menu) throws IllegalValueException { + + if (roomNumber == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, RoomNumber.class.getSimpleName())); + } + if (!RoomNumber.isValidRoomNumber(roomNumber)) { + throw new IllegalValueException(RoomNumber.MESSAGE_ROOM_NUMBER_CONSTRAINTS); + } + final RoomNumber modelRoomNumber = new RoomNumber(roomNumber); + + if (capacity == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Capacity.class.getSimpleName())); + } + final Capacity modelCapacity = capacity; + + Bookings modelBookings = new Bookings(); + try { + for (XmlAdaptedBooking b : bookings) { + modelBookings = modelBookings.add(b.toModelType()); + } + } catch (OverlappingBookingException e) { + throw new IllegalValueException(MESSAGE_OVERLAPPING_BOOKING); + } + + // only allow expenses for checked-in rooms. + // expenses are allowed for checked-in bookings that are expired; + // user may have exited the app without checking out a booking and + // the next day, the booking expired. + Expenses modelExpenses = new Expenses(); + if (!expenses.isEmpty()) { + Booking firstBooking; + try { + firstBooking = modelBookings.getFirstBooking(); + } catch (NoBookingException nbe) { + throw new IllegalValueException(MESSAGE_NO_BOOKING_TO_ADD_EXPENSES); + } + if (!firstBooking.getIsCheckedIn()) { + throw new IllegalValueException(MESSAGE_NOT_CHECKED_IN_TO_ADD_EXPENSES); + } + + final List expenseList = new ArrayList<>(); + for (XmlAdaptedExpense expense : expenses) { + expenseList.add(expense.toModelType(menu)); + } + modelExpenses = new Expenses(expenseList); + } + + final List roomTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + roomTags.add(tag.toModelType()); + } + final Set modelTags = new HashSet<>(roomTags); + + return new Room(modelRoomNumber, modelCapacity, modelExpenses, modelBookings, modelTags); + } + + @Override + public String toString() { + return roomNumber + capacity + expenses + bookings + tagged; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedRoom)) { + return false; + } + + XmlAdaptedRoom otherRoom = (XmlAdaptedRoom) other; + return Objects.equals(roomNumber, otherRoom.roomNumber) + && Objects.equals(capacity, otherRoom.capacity) + && Objects.equals(expenses, otherRoom.expenses) + && Objects.equals(bookings, otherRoom.bookings) + && tagged.equals(otherRoom.tagged); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTag.java b/src/main/java/seedu/address/storage/XmlAdaptedTag.java index d3e2d8be9c4f..32a75d41df80 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedTag.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedTag.java @@ -38,7 +38,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 guest */ 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/XmlConciergeStorage.java similarity index 55% rename from src/main/java/seedu/address/storage/XmlAddressBookStorage.java rename to src/main/java/seedu/address/storage/XmlConciergeStorage.java index ecf0e7ec23a8..d243f782ea1d 100644 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/XmlConciergeStorage.java @@ -13,47 +13,47 @@ import seedu.address.commons.exceptions.DataConversionException; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.FileUtil; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyConcierge; /** - * A class to access AddressBook data stored as an xml file on the hard disk. + * A class to access Concierge data stored as an xml file on the hard disk. */ -public class XmlAddressBookStorage implements AddressBookStorage { +public class XmlConciergeStorage implements ConciergeStorage { - private static final Logger logger = LogsCenter.getLogger(XmlAddressBookStorage.class); + private static final Logger logger = LogsCenter.getLogger(XmlConciergeStorage.class); private Path filePath; - public XmlAddressBookStorage(Path filePath) { + public XmlConciergeStorage(Path filePath) { this.filePath = filePath; } - public Path getAddressBookFilePath() { + public Path getConciergeFilePath() { return filePath; } @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(filePath); + public Optional readConcierge() throws DataConversionException, IOException { + return readConcierge(filePath); } /** - * Similar to {@link #readAddressBook()} + * Similar to {@link #readConcierge()} * @param filePath location of the data. Cannot be null * @throws DataConversionException if the file is not in the correct format. */ - public Optional readAddressBook(Path filePath) throws DataConversionException, + public Optional readConcierge(Path filePath) throws DataConversionException, FileNotFoundException { requireNonNull(filePath); if (!Files.exists(filePath)) { - logger.info("AddressBook file " + filePath + " not found"); + logger.info("Concierge file " + filePath + " not found"); return Optional.empty(); } - XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(filePath); + XmlSerializableConcierge xmlConcierge = XmlFileStorage.loadDataFromSaveFile(filePath); try { - return Optional.of(xmlAddressBook.toModelType()); + return Optional.of(xmlConcierge.toModelType()); } catch (IllegalValueException ive) { logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); throw new DataConversionException(ive); @@ -61,20 +61,20 @@ public Optional readAddressBook(Path filePath) throws DataC } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); + public void saveConcierge(ReadOnlyConcierge concierge) throws IOException { + saveConcierge(concierge, filePath); } /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} + * Similar to {@link #saveConcierge(ReadOnlyConcierge)} * @param filePath location of the data. Cannot be null */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); + public void saveConcierge(ReadOnlyConcierge concierge, Path filePath) throws IOException { + requireNonNull(concierge); requireNonNull(filePath); FileUtil.createIfMissing(filePath); - XmlFileStorage.saveDataToFile(filePath, new XmlSerializableAddressBook(addressBook)); + XmlFileStorage.saveDataToFile(filePath, new XmlSerializableConcierge(concierge)); } } diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/seedu/address/storage/XmlFileStorage.java index d8f65dc036ab..d4c3be9e8e2f 100644 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ b/src/main/java/seedu/address/storage/XmlFileStorage.java @@ -9,28 +9,28 @@ import seedu.address.commons.util.XmlUtil; /** - * Stores addressbook data in an XML file + * Stores concierge data in an XML file */ public class XmlFileStorage { /** - * Saves the given addressbook data to the specified file. + * Saves the given concierge data to the specified file. */ - public static void saveDataToFile(Path file, XmlSerializableAddressBook addressBook) + public static void saveDataToFile(Path file, XmlSerializableConcierge concierge) throws FileNotFoundException { try { - XmlUtil.saveDataToFile(file, addressBook); + XmlUtil.saveDataToFile(file, concierge); } catch (JAXBException e) { throw new AssertionError("Unexpected exception " + e.getMessage(), e); } } /** - * Returns address book in the file or an empty address book + * Returns Concierge in the file or an empty Concierge */ - public static XmlSerializableAddressBook loadDataFromSaveFile(Path file) throws DataConversionException, + public static XmlSerializableConcierge loadDataFromSaveFile(Path file) throws DataConversionException, FileNotFoundException { try { - return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); + return XmlUtil.getDataFromFile(file, XmlSerializableConcierge.class); } catch (JAXBException e) { throw new DataConversionException(e); } 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/XmlSerializableConcierge.java b/src/main/java/seedu/address/storage/XmlSerializableConcierge.java new file mode 100644 index 000000000000..d95706ebbc79 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlSerializableConcierge.java @@ -0,0 +1,132 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +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.Concierge; +import seedu.address.model.ReadOnlyConcierge; +import seedu.address.model.expenses.ExpenseType; +import seedu.address.model.guest.Guest; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.booking.Booking; +import seedu.address.model.room.exceptions.DuplicateRoomException; +import seedu.address.model.room.exceptions.RoomMissingException; + +/** + * An Immutable Concierge that is serializable to XML format + */ +@XmlRootElement(name = "concierge") +public class XmlSerializableConcierge { + + public static final String MESSAGE_DUPLICATE_GUEST = "Archived guest list contains duplicate guest(s)."; + public static final String MESSAGE_DUPLICATE_ROOM = "Room list contains duplicate room(s)."; + public static final String MESSAGE_DUPLICATE_ITEM = "Menu contains items with same number."; + public static final String MESSAGE_ROOM_MISSING = "Room list is missing room(s) from total of " + + RoomNumber.MAX_ROOM_NUMBER + " rooms."; + + @XmlElement + private List guests; + + @XmlElement(required = true) + private List rooms; + + @XmlElement + private List menu; + + /** + * Creates an empty XmlSerializableConcierge. + * This empty constructor is required for marshalling. + */ + public XmlSerializableConcierge() { + guests = new ArrayList<>(); + rooms = new ArrayList<>(); + menu = new ArrayList<>(); + } + + /** + * Conversion + */ + public XmlSerializableConcierge(ReadOnlyConcierge src) { + this(); + guests.addAll(src.getGuestList().stream().map(XmlAdaptedGuest::new).collect(Collectors.toList())); + rooms.addAll(src.getRoomList().stream().map(XmlAdaptedRoom::new).collect(Collectors.toList())); + for (Map.Entry mapping : src.getMenuMap().entrySet()) { + menu.add(new XmlAdaptedExpenseType(mapping.getValue())); + } + } + + /** + * Converts this concierge into the model's {@code Concierge} object. + * @throws IllegalValueException if there were any data constraints violated or duplicates in the + * {@code XmlAdaptedGuest / XmlAdaptedRoom} + */ + public Concierge toModelType() throws IllegalValueException { + Concierge concierge = new Concierge(); + for (XmlAdaptedGuest p : guests) { + Guest guest = p.toModelType(); + if (concierge.hasGuest(guest)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_GUEST); + } + concierge.addGuest(guest); + } + + HashMap newMenu = new HashMap<>(); + for (XmlAdaptedExpenseType expenseType : menu) { + String key = expenseType.getItemNumber(); + if (newMenu.containsKey(key)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_ITEM); + } + newMenu.put(key, expenseType.toModelType()); + } + concierge.setMenu(newMenu); + + List modelRoomList = new LinkedList<>(); + for (XmlAdaptedRoom r : rooms) { + Room room = r.toModelType(concierge.getMenu()); + modelRoomList.add(room); + + room.getBookings().getSortedBookingsSet().parallelStream() + .filter(Booking::getIsCheckedIn) + .map(Booking::getGuest) + .forEach(concierge::addCheckedInGuestIfNotPresent); + } + + try { + concierge.setRooms(modelRoomList); + } catch (DuplicateRoomException e) { + throw new IllegalValueException(MESSAGE_DUPLICATE_ROOM, e); + } catch (RoomMissingException e) { + throw new IllegalValueException(MESSAGE_ROOM_MISSING, e); + } + return concierge; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlSerializableConcierge)) { + return false; + } + return guests.equals(((XmlSerializableConcierge) other).guests) + && rooms.equals(((XmlSerializableConcierge) other).rooms) + && menu.equals(((XmlSerializableConcierge) other).menu); + } + + @Override + public int hashCode() { + return Objects.hash(guests, rooms, menu); + } +} diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java index b43de90a2b9f..db3ccf2be19f 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.GuestPanelSelectionChangedEvent; +import seedu.address.model.guest.Guest; /** * 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 loadGuestPage(Guest guest) { + loadPage(SEARCH_PAGE_URL + guest.getName().fullName); } public void loadPage(String url) { @@ -65,8 +65,8 @@ public void freeResources() { } @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { + private void handleGuestPanelSelectionChangedEvent(GuestPanelSelectionChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection()); + loadGuestPage(event.getNewSelection()); } } diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 3d7aaded5640..d80f5f33d586 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -1,5 +1,6 @@ package seedu.address.ui; +import java.util.List; import java.util.logging.Logger; import javafx.collections.ObservableList; @@ -22,11 +23,18 @@ public class CommandBox extends UiPart { public static final String ERROR_STYLE_CLASS = "error"; private static final String FXML = "CommandBox.fxml"; + private static final int DOUBLE_PRESS_DELAY = 300; + private static final String MESSAGE_AUTOCOMPLETE_AVAILABLE = "Command Suggestions: "; + private static final String MESSAGE_NO_MORE_COMMANDS_AVAILABLE = "No more commands are available"; + private static final String SPACING = " "; + private static final String EMPTY_STRING = ""; private final Logger logger = LogsCenter.getLogger(CommandBox.class); private final Logic logic; private ListElementPointer historySnapshot; + private long previousCtrlPressTime; + @FXML private TextField commandTextField; @@ -36,6 +44,7 @@ public CommandBox(Logic logic) { // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); historySnapshot = logic.getHistorySnapshot(); + previousCtrlPressTime = 0; } /** @@ -43,18 +52,30 @@ public CommandBox(Logic logic) { */ @FXML private void handleKeyPress(KeyEvent keyEvent) { + switch (keyEvent.getCode()) { case UP: // As up and down buttons will alter the position of the caret, // consuming it causes the caret's position to remain unchanged keyEvent.consume(); - navigateToPreviousInput(); break; + case DOWN: keyEvent.consume(); navigateToNextInput(); break; + + case CONTROL: + keyEvent.consume(); + autoCompleteUserInput(); + break; + + case ALT: + keyEvent.consume(); + clearCommandBox(); + break; + default: // let JavaFx handle the keypress } @@ -148,4 +169,78 @@ private void setStyleToIndicateCommandFailure() { styleClass.add(ERROR_STYLE_CLASS); } + /** + * Shows the auto-completed text or suggestions in the UI + */ + private void autoCompleteUserInput() { + if (getCurrentText().isEmpty()) { + return; + } else if (getCurrentText().endsWith(SPACING)) { + autoCompleteNextCommandParameter(); + return; + } + List listOfAutoComplete = logic.getAutoCompleteCommands(getCurrentText()); + if (listOfAutoComplete.isEmpty()) { + return; + } else if (listOfAutoComplete.size() == 1) { + replaceText(listOfAutoComplete.get(0)); + } else if (isCtrlDoubleTap()) { + showSuggestionsOnUi(listOfAutoComplete); + } + } + + /** + * Shows autocomplete suggestions on the UI given the list of string suggestions + */ + private void showSuggestionsOnUi(List listOfAutoComplete) { + logger.info(MESSAGE_AUTOCOMPLETE_AVAILABLE + commandTextField.getText() + " >> " + + getStringFromList(listOfAutoComplete)); + if (listOfAutoComplete.size() == 1) { + raise(new NewResultAvailableEvent(MESSAGE_NO_MORE_COMMANDS_AVAILABLE)); + } else { + raise(new NewResultAvailableEvent(MESSAGE_AUTOCOMPLETE_AVAILABLE + getStringFromList(listOfAutoComplete))); + } + } + + /** + * Shows auto completed next prefix parameter for completed command in UI + */ + private void autoCompleteNextCommandParameter() { + String textToShow = getCurrentText() + logic.getAutoCompleteNextParameter(getCurrentText()); + replaceText(textToShow); + } + /** + * Returns String representation of given list of strings + */ + private String getStringFromList(List listOfAutoComplete) { + String toString = listOfAutoComplete.toString(); + toString = toString.substring(1, toString.length() - 1).trim(); + return toString; + } + + /** + * Returns true if CONTROL is pressed in quick succession + */ + private boolean isCtrlDoubleTap() { + if (System.currentTimeMillis() - previousCtrlPressTime < DOUBLE_PRESS_DELAY) { + return true; + } + previousCtrlPressTime = System.currentTimeMillis(); + return false; + } + + /** + * Returns the current text in the command box + */ + private String getCurrentText() { + return commandTextField.getText(); + } + + /** + * Clear Command Box + */ + private void clearCommandBox() { + replaceText(EMPTY_STRING); + } + } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/GuestCard.java similarity index 55% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/address/ui/GuestCard.java index f6727ea83abd..1156ff3dc3f6 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/GuestCard.java @@ -5,24 +5,24 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.person.Person; +import seedu.address.model.guest.Guest; /** - * An UI component that displays information of a {@code Person}. + * An UI component that displays information of a {@code Guest}. */ -public class PersonCard extends UiPart { +public class GuestCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final String FXML = "GuestListCard.fxml"; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. * As a consequence, UI elements' variable names cannot be set to such keywords * or an exception will be thrown by JavaFX during runtime. * - * @see The issue on AddressBook level 4 + * @see The issue on Concierge level 4 */ - public final Person person; + public final Guest guest; @FXML private HBox cardPane; @@ -33,21 +33,16 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML - private Label address; - @FXML - private Label email; - @FXML private FlowPane tags; - public PersonCard(Person person, int displayedIndex) { + + public GuestCard(Guest guest, int displayedIndex) { super(FXML); - this.person = person; + this.guest = guest; 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))); + name.setText(guest.getName().fullName); + phone.setText(guest.getPhone().value); + guest.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } @Override @@ -58,13 +53,13 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof PersonCard)) { + if (!(other instanceof GuestCard)) { return false; } // state check - PersonCard card = (PersonCard) other; + GuestCard card = (GuestCard) other; return id.getText().equals(card.id.getText()) - && person.equals(card.person); + && guest.equals(card.guest); } } diff --git a/src/main/java/seedu/address/ui/GuestDetailedCard.java b/src/main/java/seedu/address/ui/GuestDetailedCard.java new file mode 100644 index 000000000000..07cd24e832c7 --- /dev/null +++ b/src/main/java/seedu/address/ui/GuestDetailedCard.java @@ -0,0 +1,66 @@ +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.guest.Guest; + +/** + * An UI component that displays information of a {@code Guest}. + */ +public class GuestDetailedCard extends UiPart { + + private static final String FXML = "GuestDetailedCard.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 Concierge level 4 + */ + + public final Guest guest; + + @FXML + private HBox cardPane; + @FXML + private Label header; + @FXML + private Label name; + @FXML + private Label phone; + @FXML + private Label email; + @FXML + private FlowPane tags; + + public GuestDetailedCard(Guest guest) { + super(FXML); + this.guest = guest; + header.setText("Guest Details:"); + name.setText(guest.getName().fullName); + phone.setText(guest.getPhone().value); + email.setText(guest.getEmail().value); + guest.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 GuestDetailedCard)) { + return false; + } + + // state check + GuestDetailedCard card = (GuestDetailedCard) other; + return guest.equals(card.guest); + } +} diff --git a/src/main/java/seedu/address/ui/GuestDetailedPanel.java b/src/main/java/seedu/address/ui/GuestDetailedPanel.java new file mode 100644 index 000000000000..8553dd79eab0 --- /dev/null +++ b/src/main/java/seedu/address/ui/GuestDetailedPanel.java @@ -0,0 +1,77 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.collections.FXCollections; +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.GuestPanelSelectionChangedEvent; +import seedu.address.model.guest.Guest; + +/** + * Panel containing the list of one guest. + */ +public class GuestDetailedPanel extends UiPart { + private static final String FXML = "GuestDetailedPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(GuestDetailedPanel.class); + + @FXML + private ListView guestDetailedView; + + public GuestDetailedPanel() { + super(FXML); + registerAsAnEventHandler(this); + } + + /** + * Sets the details of a {@code Guest} by adding into the {@code ListView} list to be + * displayed via UI. + */ + public void setGuestDetails(Guest guest) { + ObservableList guestList = FXCollections.observableArrayList(); + guestList.add(guest); + guestDetailedView.setItems(guestList); + guestDetailedView.setCellFactory(listView -> new GuestListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Guest} using a {@code GuestDetailedCard}. + */ + class GuestListViewCell extends ListCell { + @Override + protected void updateItem(Guest guest, boolean empty) { + super.updateItem(guest, empty); + + if (empty || guest == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new GuestDetailedCard(guest).getRoot()); + } + } + } + + /** + * Event handler when a guest is selected on the left panel to display detailed information on + * the right panel. + */ + @Subscribe + private void handleGuestPanelSelectionChangedEvent(GuestPanelSelectionChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + setGuestDetails(event.getNewSelection()); + } + + /** + * Clears the items from this list view + */ + public void clearSelection() { + guestDetailedView.getItems().clear(); + } + +} diff --git a/src/main/java/seedu/address/ui/GuestListPanel.java b/src/main/java/seedu/address/ui/GuestListPanel.java new file mode 100644 index 000000000000..f5ac7c194313 --- /dev/null +++ b/src/main/java/seedu/address/ui/GuestListPanel.java @@ -0,0 +1,86 @@ +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.GuestPanelSelectionChangedEvent; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.logic.parser.CliSyntax; +import seedu.address.model.guest.Guest; + +/** + * Panel containing the list of guests. + */ +public class GuestListPanel extends UiPart { + private static final String FXML = "GuestListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(GuestListPanel.class); + + @FXML + private ListView guestListView; + + public GuestListPanel(ObservableList guestList) { + super(FXML); + setConnections(guestList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList guestList) { + guestListView.setItems(guestList); + guestListView.setCellFactory(listView -> new GuestListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setEventHandlerForSelectionChangeEvent() { + guestListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in guest list panel changed to : '" + newValue + "'"); + raise(new GuestPanelSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code GuestCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + guestListView.scrollTo(index); + guestListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + if (event.flag.equals(CliSyntax.FLAG_GUEST) || event.flag.equals(CliSyntax.FLAG_CHECKED_IN_GUEST)) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Guest} using a {@code GuestCard}. + */ + class GuestListViewCell extends ListCell { + @Override + protected void updateItem(Guest guest, boolean empty) { + super.updateItem(guest, empty); + + if (empty || guest == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new GuestCard(guest, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 0e361a4d7baf..ee4fbde1e07c 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -11,6 +11,7 @@ import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; import javafx.stage.Stage; import seedu.address.commons.core.Config; import seedu.address.commons.core.GuiSettings; @@ -34,15 +35,14 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; - private PersonListPanel personListPanel; + private GuestListPanel guestListPanel; + private RoomListPanel roomListPanel; + private GuestDetailedPanel guestDetailedPanel; + private RoomDetailedPanel roomDetailedPanel; private Config config; private UserPrefs prefs; private HelpWindow helpWindow; - @FXML - private StackPane browserPlaceholder; - @FXML private StackPane commandBoxPlaceholder; @@ -50,7 +50,10 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane guestListPanelPlaceholder; + + @FXML + private StackPane roomListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -58,6 +61,25 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private VBox guestListBox; + + @FXML + private VBox roomListBox; + + @FXML + private StackPane guestDetailedPanelPlaceholder; + + @FXML + private StackPane roomDetailedPanelPlaceholder; + + @FXML + private VBox guestDetailedBox; + + @FXML + private VBox roomDetailedBox; + + public MainWindow(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { super(FXML, primaryStage); @@ -117,22 +139,32 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { /** * Fills up all the placeholders of this window. + * Initial state only displays guest list and detailed guest panel. */ void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); + guestListPanel = new GuestListPanel(logic.getFilteredGuestList()); + guestListPanelPlaceholder.getChildren().add(guestListPanel.getRoot()); - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + roomListPanel = new RoomListPanel(logic.getFilteredRoomList()); + roomListPanelPlaceholder.getChildren().add(roomListPanel.getRoot()); ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getConciergeFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(logic); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + guestDetailedPanel = new GuestDetailedPanel(); + guestDetailedPanelPlaceholder.getChildren().add(guestDetailedPanel.getRoot()); + + roomDetailedPanel = new RoomDetailedPanel(); + roomDetailedPanelPlaceholder.getChildren().add(roomDetailedPanel.getRoot()); + + this.showRoomList(); + this.showRoomDetailedPanel(); } void hide() { @@ -169,6 +201,7 @@ GuiSettings getCurrentGuiSetting() { @FXML public void handleHelp() { if (!helpWindow.isShowing()) { + helpWindow = new HelpWindow(); helpWindow.show(); } else { helpWindow.focus(); @@ -187,12 +220,72 @@ private void handleExit() { raise(new ExitAppRequestEvent()); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + /** + * UI Visibility Function - Enables guest-related list, disables room-related list UI elements + */ + public void showGuestList() { + roomListBox.setDisable(true); + roomListBox.setVisible(false); + guestListBox.setDisable(false); + guestListBox.setVisible(true); + } + + /** + * UI Visibility Functions - Enables room-related list, disables guest-related list UI elements + */ + public void showRoomList() { + roomListBox.setDisable(false); + roomListBox.setVisible(true); + guestListBox.setDisable(true); + guestListBox.setVisible(false); + } + + /** + * UI Visibility Functions - Enables guest-related detailed list, disables room-related detailed list UI elements + */ + public void showGuestDetailedPanel() { + guestDetailedBox.setDisable(false); + guestDetailedBox.setVisible(true); + roomDetailedBox.setDisable(true); + roomDetailedBox.setVisible(false); + } + + /** + * UI Visibility Functions - Enables room-related detailed list, disables guest-related detailed list UI elements + */ + public void showRoomDetailedPanel() { + guestDetailedBox.setDisable(true); + guestDetailedBox.setVisible(false); + roomDetailedBox.setDisable(false); + roomDetailedBox.setVisible(true); + } + + /** + * Sets the observable list of the guest list panel to be the list of checked-in guests + */ + public void setGuestListPanelDisplayCheckedInGuestList() { + guestListPanel = new GuestListPanel(logic.getFilteredCheckedInGuestList()); + guestListPanelPlaceholder.getChildren().add(guestListPanel.getRoot()); } - void releaseResources() { - browserPanel.freeResources(); + /** + * Sets the observable list of the guest list panel to be the list of archived guests + */ + public void setGuestListPanelDisplayGuestList() { + guestListPanel = new GuestListPanel(logic.getFilteredGuestList()); + guestListPanelPlaceholder.getChildren().add(guestListPanel.getRoot()); + } + + public GuestListPanel getGuestListPanel() { + return guestListPanel; + } + + public boolean isGuestListVisible() { + return guestListBox.isVisible(); + } + + public boolean isRoomListVisible() { + return roomListBox.isVisible(); } @Subscribe @@ -200,4 +293,18 @@ private void handleShowHelpEvent(ShowHelpRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); handleHelp(); } + + /** + * Clears the guest detailed panel + */ + public void clearGuestSelection() { + guestDetailedPanel.clearSelection(); + } + + /** + * Clears the room detailed panel + */ + public void clearRoomSelection() { + roomDetailedPanel.clearSelection(); + } } 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/RoomCard.java b/src/main/java/seedu/address/ui/RoomCard.java new file mode 100644 index 000000000000..d9a1681f20ef --- /dev/null +++ b/src/main/java/seedu/address/ui/RoomCard.java @@ -0,0 +1,65 @@ +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.room.Room; + +/** + * An UI component that displays information of a {@code Room}. + */ +public class RoomCard extends UiPart { + + private static final String FXML = "RoomListCard.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 Concierge level 4 + */ + + public final Room room; + + @FXML + private HBox roomCardPane; + @FXML + private Label roomNumber; + @FXML + private Label capacity; + @FXML + private FlowPane activeBooking; + @FXML + private FlowPane tags; + + public RoomCard(Room room) { + super(FXML); + this.room = room; + roomNumber.setText("Room: " + room.getRoomNumber().toString()); + capacity.setText("Capacity: " + room.getCapacity().toString()); + activeBooking.getChildren().add(new Label("Active booking:\n" + + room.getBookings().toStringActiveBookingShortDescription())); + room.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 RoomCard)) { + return false; + } + + // state check + RoomCard card = (RoomCard) other; + return roomNumber.getText().equals(card.roomNumber.getText()) + && room.equals(card.room); + } +} diff --git a/src/main/java/seedu/address/ui/RoomDetailedCard.java b/src/main/java/seedu/address/ui/RoomDetailedCard.java new file mode 100644 index 000000000000..21ae8c1302a2 --- /dev/null +++ b/src/main/java/seedu/address/ui/RoomDetailedCard.java @@ -0,0 +1,74 @@ +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.room.Room; + +/** + * An UI component that displays information of a {@code Room}. + */ +public class RoomDetailedCard extends UiPart { + + private static final String FXML = "RoomDetailedCard.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 Concierge level 4 + */ + + public final Room room; + + @FXML + private HBox cardPane; + @FXML + private Label header; + @FXML + private Label roomNumber; + @FXML + private Label capacity; + @FXML + private FlowPane activeBooking; + @FXML + private FlowPane allOtherBookings; + @FXML + private FlowPane tags; + @FXML + private FlowPane expenses; + + public RoomDetailedCard(Room room) { + super(FXML); + this.room = room; + header.setText("Room Details:"); + roomNumber.setText("Room: " + room.getRoomNumber().toString()); + capacity.setText("Capacity: " + room.getCapacity().toString()); + expenses.getChildren().add(new Label("Expenses:\n" + room.getExpenses().toStringSummary())); + activeBooking.getChildren().add(new Label("Active booking:\n" + room.getBookings().toStringActiveBooking())); + allOtherBookings.getChildren().add(new Label("All other bookings:\n" + + room.getBookings().toStringAllOtherBookings())); + room.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 RoomDetailedCard)) { + return false; + } + + // state check + RoomDetailedCard card = (RoomDetailedCard) other; + return roomNumber.getText().equals(card.roomNumber.getText()) + && room.equals(card.room); + } +} diff --git a/src/main/java/seedu/address/ui/RoomDetailedPanel.java b/src/main/java/seedu/address/ui/RoomDetailedPanel.java new file mode 100644 index 000000000000..45ae23201d0c --- /dev/null +++ b/src/main/java/seedu/address/ui/RoomDetailedPanel.java @@ -0,0 +1,98 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.Event; +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.RoomListChangedEvent; +import seedu.address.commons.events.ui.RoomPanelSelectionChangedEvent; +import seedu.address.model.room.Room; + + +/** + * Panel containing the list of one room. + */ +public class RoomDetailedPanel extends UiPart { + private static final String FXML = "RoomDetailedPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(RoomDetailedPanel.class); + private ObservableList displayedRoomList = FXCollections.observableArrayList(); + + @FXML + private ListView roomDetailedView; + + public RoomDetailedPanel() { + super(FXML); + registerAsAnEventHandler(this); + // Ignore events caused by typing inside the details pane. + getRoot().setOnKeyPressed(Event::consume); + } + + /** + * Sets the details of a {@code Room} by adding into the {@code ListView} list to be + * displayed via UI. + */ + private void setRoomDetails(Room room) { + displayedRoomList.clear(); + displayedRoomList.add(room); + roomDetailedView.setItems(displayedRoomList); + roomDetailedView.setCellFactory(listView -> new RoomListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code room} using a {@code RoomDetailedCard}. + */ + class RoomListViewCell extends ListCell { + @Override + protected void updateItem(Room room, boolean empty) { + super.updateItem(room, empty); + + if (empty || room == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new RoomDetailedCard(room).getRoot()); + } + } + } + + /** + * Event handler when a room is selected on the left panel to display detailed information on + * the right panel. + */ + @Subscribe + private void handleRoomPanelSelectionChangedEvent(RoomPanelSelectionChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + setRoomDetails(event.getNewSelection()); + } + + @Subscribe + private void handleRoomListChangedEvent(RoomListChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + if (displayedRoomList.isEmpty()) { + return; + } + Room displayedRoom = displayedRoomList.get(0); + ObservableList changedRoomList = event.getRoomList(); + for (Room room : changedRoomList) { + if (room.isSameRoom(displayedRoom)) { + setRoomDetails(room); + } + } + } + + /** + * Clears the items from this list view + */ + public void clearSelection() { + roomDetailedView.getItems().clear(); + } + +} diff --git a/src/main/java/seedu/address/ui/RoomListPanel.java b/src/main/java/seedu/address/ui/RoomListPanel.java new file mode 100644 index 000000000000..aac08d964d79 --- /dev/null +++ b/src/main/java/seedu/address/ui/RoomListPanel.java @@ -0,0 +1,89 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +import javafx.collections.ListChangeListener; +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.RoomListChangedEvent; +import seedu.address.commons.events.ui.RoomPanelSelectionChangedEvent; +import seedu.address.logic.parser.CliSyntax; +import seedu.address.model.room.Room; + +/** + * Panel containing the list of rooms. + */ +public class RoomListPanel extends UiPart { + private static final String FXML = "RoomListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(RoomListPanel.class); + + @FXML + private ListView roomListView; + + public RoomListPanel(ObservableList roomList) { + super(FXML); + setConnections(roomList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList roomList) { + roomList.addListener((ListChangeListener) unusedChange -> raise(new RoomListChangedEvent(roomList))); + roomListView.setItems(roomList); + roomListView.setCellFactory(listView -> new RoomListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setEventHandlerForSelectionChangeEvent() { + roomListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in room list panel changed to : '" + newValue + "'"); + raise(new RoomPanelSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code RoomCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + roomListView.scrollTo(index); + roomListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + if (event.flag.equals(CliSyntax.FLAG_ROOM)) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code room} using a {@code RoomCard}. + */ + class RoomListViewCell extends ListCell { + @Override + protected void updateItem(Room room, boolean empty) { + super.updateItem(room, empty); + + if (empty || room == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new RoomCard(room).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index f6ba29502422..d93148e5936a 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -14,7 +14,7 @@ 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.ConciergeChangedEvent; /** * A ui for the status bar that is displayed at the footer of the application. @@ -74,7 +74,7 @@ private void setSyncStatus(String status) { } @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { + public void handleConciergeChangedEvent(ConciergeChangedEvent abce) { long now = clock.millis(); String lastUpdated = new Date(now).toString(); logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 3fd3c17be156..5d82fe8ea642 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -1,5 +1,9 @@ package seedu.address.ui; +import static seedu.address.logic.parser.CliSyntax.FLAG_CHECKED_IN_GUEST; +import static seedu.address.logic.parser.CliSyntax.FLAG_GUEST; +import static seedu.address.logic.parser.CliSyntax.FLAG_ROOM; + import java.util.logging.Logger; import com.google.common.eventbus.Subscribe; @@ -14,6 +18,8 @@ import seedu.address.commons.core.Config; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.storage.DataSavingExceptionEvent; +import seedu.address.commons.events.ui.DeselectGuestListEvent; +import seedu.address.commons.events.ui.ListingChangedEvent; import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.model.UserPrefs; @@ -30,7 +36,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/concierge_icon.png"; private Logic logic; private Config config; @@ -66,7 +72,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) { @@ -117,4 +122,57 @@ private void handleDataSavingExceptionEvent(DataSavingExceptionEvent event) { showFileOperationAlertAndWait(FILE_OPS_ERROR_DIALOG_HEADER_MESSAGE, FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE, event.exception); } + + @Subscribe + private void handleListingChangeEvent(ListingChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + + if (event.getFlag().equals(FLAG_GUEST)) { + setDisplayGuestList(); + showGuestList(); + showGuestDetailedPanel(); + mainWindow.clearGuestSelection(); + } else if (event.getFlag().equals(FLAG_ROOM)) { + showRoomList(); + showRoomDetailedPanel(); + mainWindow.clearRoomSelection(); + } else if (event.getFlag().equals(FLAG_CHECKED_IN_GUEST)) { + setDisplayCheckedInGuestList(); + showGuestList(); + showGuestDetailedPanel(); + mainWindow.clearGuestSelection(); + } + } + + @Subscribe + private void handleDeselectGuestListEvent(DeselectGuestListEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + mainWindow.clearGuestSelection(); + } + + //==================== UI Visibility Functions ============================================================ + + private void showGuestList() { + mainWindow.showGuestList(); + } + + private void setDisplayCheckedInGuestList() { + mainWindow.setGuestListPanelDisplayCheckedInGuestList(); + } + + private void setDisplayGuestList() { + mainWindow.setGuestListPanelDisplayGuestList(); + } + + private void showRoomList() { + mainWindow.showRoomList(); + } + + private void showGuestDetailedPanel() { + mainWindow.showGuestDetailedPanel(); + } + + private void showRoomDetailedPanel() { + mainWindow.showRoomDetailedPanel(); + } } diff --git a/src/main/resources/images/ConciergeFinal.png b/src/main/resources/images/ConciergeFinal.png new file mode 100644 index 000000000000..e5f48282f719 Binary files /dev/null and b/src/main/resources/images/ConciergeFinal.png differ diff --git a/src/main/resources/images/SearchIcon.png b/src/main/resources/images/SearchIcon.png new file mode 100644 index 000000000000..a2f859f40d46 Binary files /dev/null and b/src/main/resources/images/SearchIcon.png differ diff --git a/src/main/resources/images/UIBackground.png b/src/main/resources/images/UIBackground.png new file mode 100644 index 000000000000..0fb339a4bf58 Binary files /dev/null and b/src/main/resources/images/UIBackground.png differ diff --git a/src/main/resources/images/UILoginSidePanel.png b/src/main/resources/images/UILoginSidePanel.png new file mode 100644 index 000000000000..90bbcd89db81 Binary files /dev/null and b/src/main/resources/images/UILoginSidePanel.png differ diff --git a/src/main/resources/images/concierge_icon.png b/src/main/resources/images/concierge_icon.png new file mode 100644 index 000000000000..7b847c09ef02 Binary files /dev/null and b/src/main/resources/images/concierge_icon.png differ diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index c8941ea18263..957dc499d53c 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -111,7 +111,7 @@ -fx-background-color: #424d5f; } -.list-cell:filled:selected #cardPane { +.list-cell:filled:selected #cardPane #roomCardPane { -fx-border-color: #3e7b91; -fx-border-width: 1; } @@ -120,10 +120,25 @@ -fx-text-fill: white; } +.cell_header { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 18px; + -fx-text-fill: #010504; + -fx-underline: true; +} + +.cell_name_label { + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 18px; + -fx-text-fill: #010504; + -fx-padding: -2 0 -2 0; +} + .cell_big_label { -fx-font-family: "Segoe UI Semibold"; -fx-font-size: 16px; -fx-text-fill: #010504; + -fx-padding: -2 0 -2 0; } .cell_small_label { @@ -306,7 +321,7 @@ -fx-padding: 8 1 8 1; } -#cardPane { +#cardPane #roomCardPane{ -fx-background-color: transparent; -fx-border-width: 0; } @@ -327,7 +342,7 @@ -fx-text-fill: white; } -#filterField, #personListPanel, #personWebpage { +#filterField, #guestListPanel, #personWebpage { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); } @@ -337,6 +352,7 @@ } #tags { + -fx-padding: 3 0 3 0; -fx-hgap: 7; -fx-vgap: 3; } @@ -346,6 +362,52 @@ -fx-background-color: #3e7b91; -fx-padding: 1 3 1 3; -fx-border-radius: 2; - -fx-background-radius: 2; + -fx-background-radius: 4; -fx-font-size: 11; } + +#expenses { + -fx-padding: 3 0 3 0; + -fx-hgap: 7; + -fx-vgap: 3; +} + +#expenses .label { + -fx-text-fill: white; + -fx-background-color: #2d7899; + -fx-padding: 3 5 3 5; + -fx-border-radius: 2; + -fx-background-radius: 4; + -fx-font-size: 13; + -fx-font-family: "Consolas", monospace; +} + +#activeBooking { + -fx-padding: 3 0 3 0; + -fx-hgap: 7; + -fx-vgap: 3; +} + +#activeBooking .label { + -fx-text-fill: white; + -fx-background-color: #3D997f; + -fx-padding: 3 5 3 5; + -fx-border-radius: 2; + -fx-background-radius: 4; + -fx-font-size: 13; +} + +#allOtherBookings { + -fx-padding: 3 0 3 0; + -fx-hgap: 7; + -fx-vgap: 3; +} + +#allOtherBookings .label { + -fx-text-fill: white; + -fx-background-color: #E39338; + -fx-padding: 3 5 3 5; + -fx-border-radius: 2; + -fx-background-radius: 4; + -fx-font-size: 13; +} diff --git a/src/main/resources/view/GuestDetailedCard.fxml b/src/main/resources/view/GuestDetailedCard.fxml new file mode 100644 index 000000000000..ddcb9bb14cae --- /dev/null +++ b/src/main/resources/view/GuestDetailedCard.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/GuestDetailedPanel.fxml b/src/main/resources/view/GuestDetailedPanel.fxml new file mode 100644 index 000000000000..35adc88d9b38 --- /dev/null +++ b/src/main/resources/view/GuestDetailedPanel.fxml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/GuestListCard.fxml similarity index 81% rename from src/main/resources/view/PersonListCard.fxml rename to src/main/resources/view/GuestListCard.fxml index f08ea32ad558..c5ff93c1af04 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/GuestListCard.fxml @@ -14,7 +14,7 @@ - + @@ -27,10 +27,8 @@ diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/GuestListPanel.fxml similarity index 77% rename from src/main/resources/view/PersonListPanel.fxml rename to src/main/resources/view/GuestListPanel.fxml index 8836d323cc5d..11cb434a1252 100644 --- a/src/main/resources/view/PersonListPanel.fxml +++ b/src/main/resources/view/GuestListPanel.fxml @@ -4,5 +4,5 @@ - + diff --git a/src/main/resources/view/LoginWindow.fxml b/src/main/resources/view/LoginWindow.fxml new file mode 100644 index 000000000000..414cac188efd --- /dev/null +++ b/src/main/resources/view/LoginWindow.fxml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index daf386d8f5b8..fdce4e0850f1 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -14,7 +14,7 @@ - + @@ -47,20 +47,43 @@ - + + + + + + + + + + + + + - + - - - - + + + + + + + + + + + + + + - + - diff --git a/src/main/resources/view/MainWindowNew.fxml b/src/main/resources/view/MainWindowNew.fxml new file mode 100644 index 000000000000..4826d24c19a7 --- /dev/null +++ b/src/main/resources/view/MainWindowNew.fxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/RoomDetailedCard.fxml b/src/main/resources/view/RoomDetailedCard.fxml new file mode 100644 index 000000000000..7190ec0fbc2d --- /dev/null +++ b/src/main/resources/view/RoomDetailedCard.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/RoomDetailedPanel.fxml b/src/main/resources/view/RoomDetailedPanel.fxml new file mode 100644 index 000000000000..9630a1838b9a --- /dev/null +++ b/src/main/resources/view/RoomDetailedPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/RoomListCard.fxml b/src/main/resources/view/RoomListCard.fxml new file mode 100644 index 000000000000..4327266c0fd7 --- /dev/null +++ b/src/main/resources/view/RoomListCard.fxml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/RoomListPanel.fxml b/src/main/resources/view/RoomListPanel.fxml new file mode 100644 index 000000000000..a6594ed843fc --- /dev/null +++ b/src/main/resources/view/RoomListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json index a312cecf8bad..7589cdb727ee 100644 --- a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json @@ -9,5 +9,5 @@ "z" : 99 } }, - "addressBookFilePath" : "addressbook.xml" + "conciergeFilePath" : "concierge.xml" } diff --git a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json index 412dbd7cac65..4afd211cfb9b 100644 --- a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json @@ -7,5 +7,5 @@ "y" : 100 } }, - "addressBookFilePath" : "addressbook.xml" + "conciergeFilePath" : "concierge.xml" } diff --git a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml deleted file mode 100644 index 41e411568a5f..000000000000 --- a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Hans Muster - 9482424 - hans@example.com -
4th street
-
- - - Hans Muster - 948asdf2424 - hans@example.com -
4th street
-
-
diff --git a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml deleted file mode 100644 index cfa128e72828..000000000000 --- a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Ha!ns Mu@ster - 9482424 - hans@example.com -
4th street
-
-
diff --git a/src/test/data/XmlAddressBookStorageTest/NotXmlFormatAddressBook.xml b/src/test/data/XmlConciergeStorageTest/NotXmlFormatConcierge.xml similarity index 100% rename from src/test/data/XmlAddressBookStorageTest/NotXmlFormatAddressBook.xml rename to src/test/data/XmlConciergeStorageTest/NotXmlFormatConcierge.xml diff --git a/src/test/data/XmlConciergeStorageTest/invalidExpenseConcierge.xml b/src/test/data/XmlConciergeStorageTest/invalidExpenseConcierge.xml new file mode 100644 index 000000000000..de01d9a61bde --- /dev/null +++ b/src/test/data/XmlConciergeStorageTest/invalidExpenseConcierge.xml @@ -0,0 +1,635 @@ + + + + Alice Pauline + 94351253 + alice@example.com + vip + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + Carl Kurz + 95352563 + heinz@example.com + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + Elle Meyer + 9482224 + werner@example.com + + + Fiona Kunz + 9482427 + lydia@example.com + + + George Best + 9482442 + anna@example.com + + + 001 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + maintenance + + + 002 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + true + + + + RS02 + 12.341 + 1/11/2018 03:17:16 + + + RS01 + 50.00 + 1/11/2018 03:16:44 + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + 011 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + false + + + + 012 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 6/11/2018 + + false + + + + 021 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + 069 + SINGLE + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + diff --git a/src/test/data/XmlConciergeStorageTest/invalidGuestAndValidRoomsConcierge.xml b/src/test/data/XmlConciergeStorageTest/invalidGuestAndValidRoomsConcierge.xml new file mode 100644 index 000000000000..3ee909f6dffe --- /dev/null +++ b/src/test/data/XmlConciergeStorageTest/invalidGuestAndValidRoomsConcierge.xml @@ -0,0 +1,592 @@ + + + + + Alic3 Paul!ne + 94351253 + alice@example.com + vip + + + 001 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + maintenance + + + 002 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + true + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + 011 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + false + + + + 012 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 6/11/2018 + + false + + + + 021 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + 069 + SINGLE + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + diff --git a/src/test/data/XmlConciergeStorageTest/invalidItemConcierge.xml b/src/test/data/XmlConciergeStorageTest/invalidItemConcierge.xml new file mode 100644 index 000000000000..710eaf52a5f4 --- /dev/null +++ b/src/test/data/XmlConciergeStorageTest/invalidItemConcierge.xml @@ -0,0 +1,672 @@ + + + + Alice Pauline + 94351253 + alice@example.com + vip + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + Carl Kurz + 95352563 + heinz@example.com + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + Elle Meyer + 9482224 + werner@example.com + + + Fiona Kunz + 9482427 + lydia@example.com + + + George Best + 9482442 + anna@example.com + + + 001 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + vip + + + 25/10/2018 + 31/10/2018 + + false + + maintenance + + + 002 + DOUBLE + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + 25/10/2018 + 31/10/2018 + + true + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Carl Kurz + 95352563 + heinz@example.com + + + 31/10/2018 + 1/11/2018 + + false + + + + 011 + SINGLE + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 1/11/2018 + 2/11/2018 + + false + + + + 012 + DOUBLE + + + Elle Meyer + 9482224 + werner@example.com + + + 1/11/2018 + 2/11/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Fiona Kunz + 9482427 + lydia@example.com + + + 1/11/2018 + 8/11/2018 + + false + + + + 021 + SINGLE + + + George Best + 9482442 + anna@example.com + + + 2/11/2018 + 8/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + vip + + + 25/10/2018 + 31/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 31/10/2018 + 1/11/2018 + + false + + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 1/11/2018 + 2/11/2018 + + false + + + + George Best + 9482442 + anna@example.com + + + 2/11/2018 + 8/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + vip + + + 25/10/2018 + 31/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 31/10/2018 + 1/11/2018 + + false + + + + Elle Meyer + 9482224 + werner@example.com + + + 1/11/2018 + 2/11/2018 + + true + + + + George Best + 9482442 + anna@example.com + + + 2/11/2018 + 8/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + 069 + SINGLE + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + + RS03 + Room service: Thai massage + 100.00 + + + MB02 + Minibar: Sprite + 3.00 + + + RS01 + Room service: Red wine + 50.00 + + + MB01 + Minibar: Coca cola + 3.00 + + + RS02 + Room service: Beef steak + 70.00 + + + SP01 + Swimming pool: Entry + 5.00 + + + MB04 + Minibar: Mineral water + 3.00 + + + + MB03 + Minibar: Tiger beer + 6.000 + + diff --git a/src/test/data/XmlConciergeStorageTest/invalidRoomAndValidGuestsConcierge.xml b/src/test/data/XmlConciergeStorageTest/invalidRoomAndValidGuestsConcierge.xml new file mode 100644 index 000000000000..6a3bcd1ad5fb --- /dev/null +++ b/src/test/data/XmlConciergeStorageTest/invalidRoomAndValidGuestsConcierge.xml @@ -0,0 +1,625 @@ + + + + Alice Pauline + 94351253 + alice@example.com + vip + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + Carl Kurz + 95352563 + heinz@example.com + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + Elle Meyer + 9482224 + werner@example.com + + + Fiona Kunz + 9482427 + lydia@example.com + + + George Best + 9482442 + anna@example.com + + + + 001 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 2018/10/23 + 2018/10/29 + + false + + maintenance + + + 002 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + true + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + 011 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + false + + + + 012 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 6/11/2018 + + false + + + + 021 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + 069 + SINGLE + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + diff --git a/src/test/data/XmlSerializableAddressBookTest/duplicatePersonAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/duplicatePersonAddressBook.xml deleted file mode 100644 index ac02230263d3..000000000000 --- a/src/test/data/XmlSerializableAddressBookTest/duplicatePersonAddressBook.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Alice Pauline - 94351253 - alice@example.com -
123, Jurong West Ave 6, #08-111
- friends -
- - - - Alice Pauline - 94351253 - pauline@example.com -
4th street
-
- -
diff --git a/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml deleted file mode 100644 index 13d5b1cb1c8a..000000000000 --- a/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Hans Muster - 9482424 - hans@exam!32ple -
4th street
-
-
diff --git a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml deleted file mode 100644 index d812b05e32bb..000000000000 --- a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - Alice Pauline - 94351253 - alice@example.com -
123, Jurong West Ave 6, #08-111
- friends -
- - Benson Meier - 98765432 - johnd@example.com -
311, Clementi Ave 2, #02-25
- owesMoney - friends -
- - Carl Kurz - 95352563 - heinz@example.com -
wall street
-
- - Daniel Meier - 87652533 - cornelia@example.com -
10th street
- friends -
- - Elle Meyer - 9482224 - werner@example.com -
michegan ave
-
- - Fiona Kunz - 9482427 - lydia@example.com -
little tokyo
-
- - George Best - 9482442 - anna@example.com -
4th street
-
-
diff --git a/src/test/data/XmlSerializableConciergeTest/duplicateGuestConcierge.xml b/src/test/data/XmlSerializableConciergeTest/duplicateGuestConcierge.xml new file mode 100644 index 000000000000..dba312316d4e --- /dev/null +++ b/src/test/data/XmlSerializableConciergeTest/duplicateGuestConcierge.xml @@ -0,0 +1,635 @@ + + + + Alice Pauline + 94351253 + alice@example.com + vip + + + + Alice Pauline + 94351253 + pauline@example.com + vip + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + Carl Kurz + 95352563 + heinz@example.com + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + Elle Meyer + 9482224 + werner@example.com + + + Fiona Kunz + 9482427 + lydia@example.com + + + George Best + 9482442 + anna@example.com + + + 001 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + maintenance + + + 002 + DOUBLE + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + 23/10/2018 + 29/10/2018 + + true + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + 011 + SINGLE + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 30/10/2018 + 31/10/2018 + + false + + + + 012 + DOUBLE + + + Elle Meyer + 9482224 + werner@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Fiona Kunz + 9482427 + lydia@example.com + + + 30/10/2018 + 6/11/2018 + + false + + + + 021 + SINGLE + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 30/10/2018 + 31/10/2018 + + false + + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Elle Meyer + 9482224 + werner@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + 069 + SINGLE + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + diff --git a/src/test/data/XmlSerializableConciergeTest/duplicateItemConcierge.xml b/src/test/data/XmlSerializableConciergeTest/duplicateItemConcierge.xml new file mode 100644 index 000000000000..187cec26359e --- /dev/null +++ b/src/test/data/XmlSerializableConciergeTest/duplicateItemConcierge.xml @@ -0,0 +1,90 @@ + + + + + Alice Pauline + 94351253 + alice@example.com +
123, Jurong West Ave 6, #08-111
+ friends +
+ + Benson Meier + 98765432 + johnd@example.com +
311, Clementi Ave 2, #02-25
+ owesMoney + friends +
+ + Carl Kurz + 95352563 + heinz@example.com +
wall street
+
+ + Daniel Meier + 87652533 + cornelia@example.com +
10th street
+ friends +
+ + Elle Meyer + 9482224 + werner@example.com +
michegan ave
+
+ + Fiona Kunz + 9482427 + lydia@example.com +
little tokyo
+
+ + George Best + 9482442 + anna@example.com +
4th street
+
+ + RS03 + Room service: Thai massage + 100.00 + + + RS01 + Room service: Red wine + 50.00 + + + MB02 + Minibar: Sprite + 3.00 + + + RS02 + Room service: Beef steak + 70.00 + + + MB03 + Minibar: Coca cola + 3.00 + + + SP01 + Swimming pool: Entry + 5.00 + + + MB04 + Minibar: Mineral water + 3.00 + + + MB03 + Minibar: Tiger beer + 6.00 + +
diff --git a/src/test/data/XmlSerializableConciergeTest/duplicateRoomConcierge.xml b/src/test/data/XmlSerializableConciergeTest/duplicateRoomConcierge.xml new file mode 100644 index 000000000000..4befadcf80fc --- /dev/null +++ b/src/test/data/XmlSerializableConciergeTest/duplicateRoomConcierge.xml @@ -0,0 +1,629 @@ + + + + Alice Pauline + 94351253 + alice@example.com + vip + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + Carl Kurz + 95352563 + heinz@example.com + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + Elle Meyer + 9482224 + werner@example.com + + + Fiona Kunz + 9482427 + lydia@example.com + + + George Best + 9482442 + anna@example.com + + + 001 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + maintenance + + + + 001 + DOUBLE + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + 23/10/2018 + 29/10/2018 + + true + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + 011 + SINGLE + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 30/10/2018 + 31/10/2018 + + false + + + + 012 + DOUBLE + + + Elle Meyer + 9482224 + werner@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Fiona Kunz + 9482427 + lydia@example.com + + + 30/10/2018 + 6/11/2018 + + false + + + + 021 + SINGLE + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 30/10/2018 + 31/10/2018 + + false + + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Elle Meyer + 9482224 + werner@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + 069 + SINGLE + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + diff --git a/src/test/data/XmlSerializableConciergeTest/invalidExpenseConcierge.xml b/src/test/data/XmlSerializableConciergeTest/invalidExpenseConcierge.xml new file mode 100644 index 000000000000..43db5db008d7 --- /dev/null +++ b/src/test/data/XmlSerializableConciergeTest/invalidExpenseConcierge.xml @@ -0,0 +1,636 @@ + + + + Alice Pauline + 94351253 + alice@example.com + vip + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + Carl Kurz + 95352563 + heinz@example.com + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + Elle Meyer + 9482224 + werner@example.com + + + Fiona Kunz + 9482427 + lydia@example.com + + + George Best + 9482442 + anna@example.com + + + 001 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + maintenance + + + 002 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 31/12/2099 + + + true + + + + RS02 + 12.341 + 1/11/2018 03:17:16 + + + RS01 + 50.00 + 1/11/2018 03:16:44 + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + 011 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + false + + + + 012 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 6/11/2018 + + false + + + + 021 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Alice Pauline + 94351253 + alice@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + Alice Pauline + 94351253 + alice@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + 069 + SINGLE + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + diff --git a/src/test/data/XmlSerializableConciergeTest/invalidGuestConcierge.xml b/src/test/data/XmlSerializableConciergeTest/invalidGuestConcierge.xml new file mode 100644 index 000000000000..735f6b5beb84 --- /dev/null +++ b/src/test/data/XmlSerializableConciergeTest/invalidGuestConcierge.xml @@ -0,0 +1,629 @@ + + + + + Alice Pauline + 94351253 + alice@examp!e.com + vip + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + Carl Kurz + 95352563 + heinz@example.com + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + Elle Meyer + 9482224 + werner@example.com + + + Fiona Kunz + 9482427 + lydia@example.com + + + George Best + 9482442 + anna@example.com + + + 001 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + maintenance + + + 002 + DOUBLE + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + 23/10/2018 + 29/10/2018 + + true + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + 011 + SINGLE + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 30/10/2018 + 31/10/2018 + + false + + + + 012 + DOUBLE + + + Elle Meyer + 9482224 + werner@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Fiona Kunz + 9482427 + lydia@example.com + + + 30/10/2018 + 6/11/2018 + + false + + + + 021 + SINGLE + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 30/10/2018 + 31/10/2018 + + false + + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Elle Meyer + 9482224 + werner@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + 069 + SINGLE + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + diff --git a/src/test/data/XmlSerializableConciergeTest/invalidItemConcierge.xml b/src/test/data/XmlSerializableConciergeTest/invalidItemConcierge.xml new file mode 100644 index 000000000000..710eaf52a5f4 --- /dev/null +++ b/src/test/data/XmlSerializableConciergeTest/invalidItemConcierge.xml @@ -0,0 +1,672 @@ + + + + Alice Pauline + 94351253 + alice@example.com + vip + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + Carl Kurz + 95352563 + heinz@example.com + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + Elle Meyer + 9482224 + werner@example.com + + + Fiona Kunz + 9482427 + lydia@example.com + + + George Best + 9482442 + anna@example.com + + + 001 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + vip + + + 25/10/2018 + 31/10/2018 + + false + + maintenance + + + 002 + DOUBLE + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + 25/10/2018 + 31/10/2018 + + true + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Carl Kurz + 95352563 + heinz@example.com + + + 31/10/2018 + 1/11/2018 + + false + + + + 011 + SINGLE + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 1/11/2018 + 2/11/2018 + + false + + + + 012 + DOUBLE + + + Elle Meyer + 9482224 + werner@example.com + + + 1/11/2018 + 2/11/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Fiona Kunz + 9482427 + lydia@example.com + + + 1/11/2018 + 8/11/2018 + + false + + + + 021 + SINGLE + + + George Best + 9482442 + anna@example.com + + + 2/11/2018 + 8/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + vip + + + 25/10/2018 + 31/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 31/10/2018 + 1/11/2018 + + false + + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 1/11/2018 + 2/11/2018 + + false + + + + George Best + 9482442 + anna@example.com + + + 2/11/2018 + 8/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + vip + + + 25/10/2018 + 31/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 31/10/2018 + 1/11/2018 + + false + + + + Elle Meyer + 9482224 + werner@example.com + + + 1/11/2018 + 2/11/2018 + + true + + + + George Best + 9482442 + anna@example.com + + + 2/11/2018 + 8/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + 069 + SINGLE + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + + RS03 + Room service: Thai massage + 100.00 + + + MB02 + Minibar: Sprite + 3.00 + + + RS01 + Room service: Red wine + 50.00 + + + MB01 + Minibar: Coca cola + 3.00 + + + RS02 + Room service: Beef steak + 70.00 + + + SP01 + Swimming pool: Entry + 5.00 + + + MB04 + Minibar: Mineral water + 3.00 + + + + MB03 + Minibar: Tiger beer + 6.000 + + diff --git a/src/test/data/XmlSerializableConciergeTest/invalidRoomConcierge.xml b/src/test/data/XmlSerializableConciergeTest/invalidRoomConcierge.xml new file mode 100644 index 000000000000..893409ca1b50 --- /dev/null +++ b/src/test/data/XmlSerializableConciergeTest/invalidRoomConcierge.xml @@ -0,0 +1,628 @@ + + + + Alice Pauline + 94351253 + alice@example.com + vip + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + Carl Kurz + 95352563 + heinz@example.com + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + Elle Meyer + 9482224 + werner@example.com + + + Fiona Kunz + 9482427 + lydia@example.com + + + George Best + 9482442 + anna@example.com + + + + 001 + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + maintenance + + + 002 + DOUBLE + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + 23/10/2018 + 29/10/2018 + + true + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + 011 + SINGLE + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 30/10/2018 + 31/10/2018 + + false + + + + 012 + DOUBLE + + + Elle Meyer + 9482224 + werner@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Fiona Kunz + 9482427 + lydia@example.com + + + 30/10/2018 + 6/11/2018 + + false + + + + 021 + SINGLE + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 30/10/2018 + 31/10/2018 + + false + + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Elle Meyer + 9482224 + werner@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + 069 + SINGLE + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + diff --git a/src/test/data/XmlSerializableConciergeTest/missingRoomConcierge.xml b/src/test/data/XmlSerializableConciergeTest/missingRoomConcierge.xml new file mode 100644 index 000000000000..729e8b374bda --- /dev/null +++ b/src/test/data/XmlSerializableConciergeTest/missingRoomConcierge.xml @@ -0,0 +1,668 @@ + + + + Alice Pauline + 94351253 + alice@example.com + vip + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + Carl Kurz + 95352563 + heinz@example.com + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + Elle Meyer + 9482224 + werner@example.com + + + Fiona Kunz + 9482427 + lydia@example.com + + + George Best + 9482442 + anna@example.com + + + 001 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + vip + + + 25/10/2018 + 31/10/2018 + + false + + maintenance + + + 002 + DOUBLE + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + 25/10/2018 + 31/10/2018 + + true + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Carl Kurz + 95352563 + heinz@example.com + + + 31/10/2018 + 1/11/2018 + + false + + + + 011 + SINGLE + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 1/11/2018 + 2/11/2018 + + false + + + + 012 + DOUBLE + + + Elle Meyer + 9482224 + werner@example.com + + + 1/11/2018 + 2/11/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Fiona Kunz + 9482427 + lydia@example.com + + + 1/11/2018 + 8/11/2018 + + false + + + + 021 + SINGLE + + + George Best + 9482442 + anna@example.com + + + 2/11/2018 + 8/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + vip + + + 25/10/2018 + 31/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 31/10/2018 + 1/11/2018 + + false + + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 1/11/2018 + 2/11/2018 + + false + + + + George Best + 9482442 + anna@example.com + + + 2/11/2018 + 8/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + vip + + + 25/10/2018 + 31/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 31/10/2018 + 1/11/2018 + + false + + + + Elle Meyer + 9482224 + werner@example.com + + + 1/11/2018 + 2/11/2018 + + true + + + + George Best + 9482442 + anna@example.com + + + 2/11/2018 + 8/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + + RS03 + Room service: Thai massage + 100.00 + + + MB02 + Minibar: Sprite + 3.00 + + + RS01 + Room service: Red wine + 50.00 + + + MB01 + Minibar: Coca cola + 3.00 + + + RS02 + Room service: Beef steak + 70.00 + + + SP01 + Swimming pool: Entry + 5.00 + + + MB04 + Minibar: Mineral water + 3.00 + + + MB03 + Minibar: Tiger beer + 6.00 + + diff --git a/src/test/data/XmlSerializableConciergeTest/overlappingBookingConcierge.xml b/src/test/data/XmlSerializableConciergeTest/overlappingBookingConcierge.xml new file mode 100644 index 000000000000..b0141dada8bb --- /dev/null +++ b/src/test/data/XmlSerializableConciergeTest/overlappingBookingConcierge.xml @@ -0,0 +1,641 @@ + + + + Alice Pauline + 94351253 + alice@example.com + vip + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + Carl Kurz + 95352563 + heinz@example.com + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + Elle Meyer + 9482224 + werner@example.com + + + Fiona Kunz + 9482427 + lydia@example.com + + + George Best + 9482442 + anna@example.com + + + 001 + SINGLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + + Mary Jane + 12345678 + mary@example.com + + + 23/10/2018 + 29/10/2018 + + false + + maintenance + + + 002 + DOUBLE + + + Benson Meier + 98765432 + johnd@example.com + vip + specialNeeds + + + 23/10/2018 + 29/10/2018 + + true + + maintenance + + + 003 + SINGLE + + + 004 + DOUBLE + + + 005 + SINGLE + + + 006 + DOUBLE + + + 007 + SINGLE + + + 008 + DOUBLE + + + 009 + SINGLE + + + 010 + SUITE + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + 011 + SINGLE + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 30/10/2018 + 31/10/2018 + + false + + + + 012 + DOUBLE + + + Elle Meyer + 9482224 + werner@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + 013 + SINGLE + + + 014 + DOUBLE + + + 015 + SINGLE + + + 016 + DOUBLE + + + 017 + SINGLE + + + 018 + DOUBLE + + + 019 + SINGLE + + + 020 + SUITE + + + Fiona Kunz + 9482427 + lydia@example.com + + + 30/10/2018 + 6/11/2018 + + false + + + + 021 + SINGLE + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 022 + DOUBLE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Daniel Meier + 87652533 + cornelia@example.com + friends + + + 30/10/2018 + 31/10/2018 + + false + + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 023 + SINGLE + + + 024 + DOUBLE + + + 025 + SINGLE + + + 026 + DOUBLE + + + 027 + SINGLE + + + 028 + DOUBLE + + + 029 + SINGLE + + + 030 + SUITE + + + Alice Pauline + 94351253 + alice@example.com + + + 23/10/2018 + 29/10/2018 + + false + + + + Carl Kurz + 95352563 + heinz@example.com + + + 29/10/2018 + 30/10/2018 + + false + + + + Elle Meyer + 9482224 + werner@example.com + + + 30/10/2018 + 31/10/2018 + + true + + + + George Best + 9482442 + anna@example.com + + + 31/10/2018 + 6/11/2018 + + false + + + + 031 + SINGLE + + + 032 + DOUBLE + + + 033 + SINGLE + + + 034 + DOUBLE + + + 035 + SINGLE + + + 036 + DOUBLE + + + 037 + SINGLE + + + 038 + DOUBLE + + + 039 + SINGLE + + + 040 + SUITE + + + 041 + SINGLE + + + 042 + DOUBLE + + + 043 + SINGLE + + + 044 + DOUBLE + + + 045 + SINGLE + + + 046 + DOUBLE + + + 047 + SINGLE + + + 048 + DOUBLE + + + 049 + SINGLE + + + 050 + SUITE + + + 051 + SINGLE + + + 052 + DOUBLE + + + 053 + SINGLE + + + 054 + DOUBLE + + + 055 + SINGLE + + + 056 + DOUBLE + + + 057 + SINGLE + + + 058 + DOUBLE + + + 059 + SINGLE + + + 060 + SUITE + + + 061 + SINGLE + + + 062 + DOUBLE + + + 063 + SINGLE + + + 064 + DOUBLE + + + 065 + SINGLE + + + 066 + DOUBLE + + + 067 + SINGLE + + + 068 + DOUBLE + + + 069 + SINGLE + + + 070 + SUITE + + + 071 + SINGLE + + + 072 + DOUBLE + + + 073 + SINGLE + + + 074 + DOUBLE + + + 075 + SINGLE + + + 076 + DOUBLE + + + 077 + SINGLE + + + 078 + DOUBLE + + + 079 + SINGLE + + + 080 + SUITE + + + 081 + SINGLE + + + 082 + DOUBLE + + + 083 + SINGLE + + + 084 + DOUBLE + + + 085 + SINGLE + + + 086 + DOUBLE + + + 087 + SINGLE + + + 088 + DOUBLE + + + 089 + SINGLE + + + 090 + SUITE + + + 091 + SINGLE + + + 092 + DOUBLE + + + 093 + SINGLE + + + 094 + DOUBLE + + + 095 + SINGLE + + + 096 + DOUBLE + + + 097 + SINGLE + + + 098 + DOUBLE + + + 099 + SINGLE + + + 100 + SUITE + + diff --git a/src/test/data/XmlUtilTest/invalidGuestField.xml b/src/test/data/XmlUtilTest/invalidGuestField.xml new file mode 100644 index 000000000000..74a57ba0973a --- /dev/null +++ b/src/test/data/XmlUtilTest/invalidGuestField.xml @@ -0,0 +1,7 @@ + + + Alice Pauline + 9482asf424 + alice@example.com + vip + diff --git a/src/test/data/XmlUtilTest/invalidPersonField.xml b/src/test/data/XmlUtilTest/invalidPersonField.xml deleted file mode 100644 index ba49c971e884..000000000000 --- a/src/test/data/XmlUtilTest/invalidPersonField.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - Hans Muster - 9482asf424 - hans@example -
4th street
- friends -
diff --git a/src/test/data/XmlUtilTest/missingGuestField.xml b/src/test/data/XmlUtilTest/missingGuestField.xml new file mode 100644 index 000000000000..ee02fe94ad4a --- /dev/null +++ b/src/test/data/XmlUtilTest/missingGuestField.xml @@ -0,0 +1,7 @@ + + + + 94351253 + alice@example.com + vip + diff --git a/src/test/data/XmlUtilTest/missingPersonField.xml b/src/test/data/XmlUtilTest/missingPersonField.xml deleted file mode 100644 index c0da5c86d080..000000000000 --- a/src/test/data/XmlUtilTest/missingPersonField.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - 9482424 - hans@example -
4th street
- friends -
diff --git a/src/test/data/XmlUtilTest/missingRoomField.xml b/src/test/data/XmlUtilTest/missingRoomField.xml new file mode 100644 index 000000000000..29f3000344ac --- /dev/null +++ b/src/test/data/XmlUtilTest/missingRoomField.xml @@ -0,0 +1,5 @@ + + + 001 + maintenance + diff --git a/src/test/data/XmlUtilTest/tempAddressBook.xml b/src/test/data/XmlUtilTest/tempConcierge.xml similarity index 82% rename from src/test/data/XmlUtilTest/tempAddressBook.xml rename to src/test/data/XmlUtilTest/tempConcierge.xml index 4773cf598f4b..1b923e7d975e 100644 --- a/src/test/data/XmlUtilTest/tempAddressBook.xml +++ b/src/test/data/XmlUtilTest/tempConcierge.xml @@ -1,6 +1,6 @@ - - + + 1 John Doe @@ -8,5 +8,5 @@ - - + + diff --git a/src/test/data/XmlUtilTest/validAddressBook.xml b/src/test/data/XmlUtilTest/validAddressBook.xml deleted file mode 100644 index 6265778674d3..000000000000 --- a/src/test/data/XmlUtilTest/validAddressBook.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - Hans Muster - 9482424 - hans@example.com -
4th street
-
- - Ruth Mueller - 87249245 - ruth@example.com -
81th street
-
- - Heinz Kurz - 95352563 - heinz@example.com -
wall street
-
- - Cornelia Meier - 87652533 - cornelia@example.com -
10th street
-
- - Werner Meyer - 9482224 - werner@example.com -
michegan ave
-
- - Lydia Kunz - 9482427 - lydia@example.com -
little tokyo
-
- - Anna Best - 9482442 - anna@example.com -
4th street
-
- - Stefan Meier - 8482424 - stefan@example.com -
little india
-
- - Martin Mueller - 8482131 - hans@example.com -
chicago ave
-
-
diff --git a/src/test/data/XmlUtilTest/validGuest.xml b/src/test/data/XmlUtilTest/validGuest.xml new file mode 100644 index 000000000000..8438cc11db41 --- /dev/null +++ b/src/test/data/XmlUtilTest/validGuest.xml @@ -0,0 +1,7 @@ + + + Alice Pauline + 94351253 + alice@example.com + vip + diff --git a/src/test/data/XmlUtilTest/validPerson.xml b/src/test/data/XmlUtilTest/validPerson.xml deleted file mode 100644 index c029008d54f4..000000000000 --- a/src/test/data/XmlUtilTest/validPerson.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - Hans Muster - 9482424 - hans@example -
4th street
- friends -
diff --git a/src/test/java/guitests/guihandles/GuestCardHandle.java b/src/test/java/guitests/guihandles/GuestCardHandle.java new file mode 100644 index 000000000000..0d80ab177086 --- /dev/null +++ b/src/test/java/guitests/guihandles/GuestCardHandle.java @@ -0,0 +1,72 @@ +package guitests.guihandles; + +import java.util.List; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableMultiset; + +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.guest.Guest; + +/** + * Provides a handle to a guest card in the guest list panel. + */ +public class GuestCardHandle extends NodeHandle { + private static final String ID_FIELD_ID = "#id"; + private static final String NAME_FIELD_ID = "#name"; + private static final String PHONE_FIELD_ID = "#phone"; + private static final String TAGS_FIELD_ID = "#tags"; + + private final Label idLabel; + private final Label nameLabel; + private final Label phoneLabel; + private final List