diff --git a/README.md b/README.md index 13f5c77403f..a87c63aac6d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,8 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2122S1-CS2103T-T13-3/tp/actions/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S1-CS2103T-T13-3/tp/actions) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
+The project is a desktop application (called Trace2Gather) used for managing a hotel, +for ease of tracking guests and rooms status for contact tracing.
+For the detailed documentation of this project, see the **[Address Book Product Website](https://ay2122s1-cs2103t-t13-3.github.io/tp/)**. diff --git a/build.gradle b/build.gradle index be2d2905dde..d006a29368f 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,11 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'trace2gather.jar' +} + +run { + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..1fc62c3fd78 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -5,55 +5,53 @@ title: About Us We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). -You can reach us at the email `seer[at]comp.nus.edu.sg` - ## Project team -### John Doe +### Darren Hoon - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/darrenhoon)] +[[portfolio](team/darrenhoon.md)] -* Role: Project Advisor +* Role: Developer +* Responsibilities: DevOps, UI -### Jane Doe +### Liew Jian Hong - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/jianh0ng)] [[portfolio](team/jianh0ng.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: Data -### Johnny Doe +### Ye Pei Lin - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/peilinye)] +[[portfolio](team/peilinye.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Data, UI -### Jean Doe +### Fong Mun Kit, Wilbur - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/wilburrito)] +[[portfolio](team/wilburrito.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: DevOps, Data -### James Doe +### Thomas Edward Hogben - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/BananaTechs)] +[[portfolio](team/bananatechs.md)] -* Role: Developer -* Responsibilities: UI +* Role: **Developer** +* Responsibilities: **Data** diff --git a/docs/Configuration.md b/docs/Configuration.md index 13cf0faea16..817d9a9e579 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1,6 +1,6 @@ --- layout: page -title: Configuration guide +title: Configuration Guide --- Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the configuration file (default: `config.json`). diff --git a/docs/DevOps.md b/docs/DevOps.md index ca59d92f2cb..0e1c44a773c 100644 --- a/docs/DevOps.md +++ b/docs/DevOps.md @@ -1,6 +1,6 @@ --- layout: page -title: DevOps guide +title: DevOps Guide --- * Table of Contents diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..ce4ad5c6dbc 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -9,10 +9,15 @@ title: Developer Guide ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +This project was based on the [_AddressBook Level-3_](https://se-education.org/addressbook-level3/) project created by the SE-EDU initiative. +
Libraries used: [_JavaFX_](https://openjfx.io/), [_Jackson_](https://github.com/FasterXML/jackson), [_JUnit5_](https://github.com/junit-team/junit5) -------------------------------------------------------------------------------------------------------------------- +## **Introduction** +Welcome to the developer's guide for Trace2Gather! Trace2Gather is a **desktop app for managing hotel rooms and guests, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). +
This guide is meant for developers who may want to contribute to our code base, or use our codebase to build their own project. +-------------------------------------------------------------------------------------------------------------------- ## **Setting up, getting started** Refer to the guide [_Setting up and getting started_](SettingUp.md). @@ -21,6 +26,8 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md). ## **Design** +This section shows the various high level components that make up the application, how they interact with one another, and their lower level implementation. +
:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. @@ -52,14 +59,14 @@ The rest of the App consists of four components. **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `guest Alex`. - + Each of the four main components (also shown in the diagram above), * defines its *API* in an `interface` with the same name as the Component. -* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point). For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. @@ -96,11 +103,11 @@ How the `Logic` component works: 1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command. 1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. 1. The command can communicate with the `Model` when it is executed (e.g. to add a person). -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +1. The result of the command execution is encapsulated as a `CommandResult` object which is returned from `Logic`. -The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. +The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("guest Alex")` API call. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `guest Alex` Command](images/FindGuestSequenceDiagram.png)
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
@@ -121,16 +128,17 @@ How the parsing works: The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the address book data. + * All `Person` objects are contained in a `UniquePersonList` object. + * All `Room` objects are contained in a `RoomList` object. + * All `Residency` objects are contained in a `ResidencyBook` object. +* stores the currently 'selected' object(s) (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as 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. + * `Person` objects are stored in `ObservableList` + * `Room` objects are stored in `ObservableList` + * `Residency` objects are stored in `ObservableList` * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. * does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) -
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
- - - -
### Storage component @@ -154,90 +162,131 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Guest and Room search feature -#### Proposed Implementation +#### Implementation -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +The search mechanism is facilitated by `LogicManager`. It extends `Logic` and its invocation is via the `AddressBookParser`. -* `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. +* `AddressBookParser#parseCommand()` — Interprets the command the user inputs to invoke the `FindGuestCommand` and `FindRoomCommand`. +* `FindGuestCommand#execute()` — Finds the guest in the hotel with matching name +* `FindRoomCommand#execute()` — Finds the room in the hotel with matching room number These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +Given below is an example usage scenario: -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. +1. User searches for the data entry desired. In this case, the user's input is "guest Alex" -![UndoRedoState0](images/UndoRedoState0.png) +2. Hit Enter. -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. +3. The Rooms / Guests that have matching names will appear in their respective lists. -![UndoRedoState1](images/UndoRedoState1.png) +The behaviour of the search mechanism is illustrated by the following sequence diagram. -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`. +![Interactions Inside the Logic Component for the `guest Alex` Command](images/FindGuestSequenceDiagram.png) -![UndoRedoState2](images/UndoRedoState2.png) +#### Design considerations: -
:information_source: **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`. +**Aspect: How search guest / room executes:** -
+* The string name / room number will be passed into a predicate checker to check if any of the data present contains the information as requested. + * Pros: Consistent implementation - similar to the other commands. + * Cons: Increased need for good file system and extensive application of Object-Oriented Principles required. -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. -![UndoRedoState3](images/UndoRedoState3.png) +### Listing rooms by vacancy feature -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook 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. +#### Implementation -
+The list mechanism is facilitated by `LogicManager`. It extends `Logic` and its invocation is via the `AddressBookParser`. +It implements the following key operations. -The following sequence diagram shows how the undo operation works: +* `AddressBookParser#parseCommand()` — Interprets the command the user inputs to invoke the `ListCommand`. +* `ListCommand#execute()` — Executes the relevant `ListCommand`. +* `Model#updateFilteredRoomList()` — Filters the list of rooms based on their vacancy status and updates the internal list of rooms to be displayed by the UI. -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +This operation is exposed in the `Model` interface as `Model#updateFilteredRoomList()`. -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +The following sequence diagram shows the interactions between objects of the Logic component for the list vacant room mechanism. -
+![Interactions Inside the Logic Component for the `list rooms vacant` Command](images/ListRoomsByVacancySequenceDiagram.png) + +The `list rooms occupied` command works the same way, except a `RoomIsOccupiedPredicate` is passed as argument when calling `Model#updateFilteredRoomList()`. +The rooms of the specified vacancy status will appear in the room list. -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. +#### Design considerations: -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook 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. +**Aspect: How list room occupied / vacant executes:** -
+* The `ParserUtil` checks that the `ListRoomArg` is valid (either "occupied" or "vacant" and not any other arguments), and the `ListCommandParser` creates a predicate object for `Model#updateFilteredRoomList()` to filter the rooms based on. + * Pros: Consistency - similar implementation as the command to list all rooms, list all guests and list all records. + * Cons: This implementation does not fully adhere to OOP principles like inheritance. No new command classes such as `ListVacantRoomCommand` and `ListOccupiedRoomCommand`. -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. +### Uniqueness of Guests -![UndoRedoState4](images/UndoRedoState4.png) +#### Implementation -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. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +The mechanism guaranteeing the uniqueness of Guests is facilitated by the `Nric` class, and its invocation is via `AddressBookParser`. +* `AddressBookParser#parseCommand()`  — Interprets the command the user inputs to invoke the `AddCommandParser`. +* `ParserUtil#parseNric()`  — checks whether a `Person` object already exists with the same Nric. -![UndoRedoState5](images/UndoRedoState5.png) +#### Design considerations: -The following activity diagram summarizes what happens when a user executes a new command: +**Aspect: How duplicates are avoided:** + +* An `AddCommand` that wants to add a `Person` with an `Nric` that another existing `Person` already will be considered an invalid command. + * Uniqueness  —  This mechanism will help to prevent the adding of duplicate `Person` objects. + +### Encapsulation of Hotel Stays (The Residency System) + +#### Implementation + +The stay of guests in a room for a period of time is encapsulated in the `Residency` class, and `Residency` objects are created and handled by the `ResidencyBook` class. Creation of `Residency` objects is invoked via `CheckInCommand`. - #### Design considerations: -**Aspect: How undo & redo executes:** +**Aspect: The Immutability of Person and Room objects** +* Due to the immutability of these objects, it is difficult to have them store references to each other. + The creation of the `Residency` association class was thus necessary, and also allows additional information about stays to be stored, such as dates and times of check in and check out, among other possible future features. + +**Aspect: Storing References to Person and Room objects** +* Due to `Residency` objects needing to store references to `Person` and `Room` objects, an identification system for the latter two classes had to be created to facilitate JSON storage of the actual references. + Otherwise, the JSON would only store copies of the `Person` and `Room` objects, which would mean that editing a `Person`'s details via the edit command would not affect the `Person` copy in the `Residency`. + +**Aspect: Further Expansion to Store Past Records** +* For current hotel stays, each room can only have one set of guests checked in at any given time, and likewise, any guest should only be checked into one room at a time. + It thus follows that the `ResidencyBook` class should ensure that each Person and Room object can only be referenced in one Residency at any given time. +

+ However, the `ResidencyBook` should have the ability to accommodate the Past Records Feature mentioned below, which will involve multiple `Residency` objects referencing the same room. + Hence, `ResidencyBook` has a boolean parameter in its constructor for allowing duplicates. -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +### Past Records Feature -* **Alternative 2:** Individual command knows how to undo/redo by - itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. +This section describes how past residencies are stored such that it can be displayed/searched for contact tracing. -_{more aspects and alternatives to be added}_ +The past residencies are read from the same json data file as the other components in the `AddressBook`, through the `JsonAdaptedResidencyBook` class. -### \[Proposed\] Data archiving +Past residencies can be searched through the use of the `record` command, where any number of keywords can be entered and any record matching all the keywords are displayed to the user. -_{Explain here how the data archiving feature will be implemented}_ +Given below is an example of the search function for all the past residencies of room 001. +![Sequence Diagram of record command](images/RecordCommandSequenceDiagram.png) + + +#### Design considerations: + +* Possible location of storage of past residencies in a second file. + * Pros: Keeping past residency storage separate from the main data storage minimises any mix up in the storing of information. + * Cons: This requires the file to store its own set of persons and rooms and because the residency keeps minimal information in order to minimise + space required for the storage file, it results in redundancy when storing the same information across 2 files. Changes also have to be written twice. + +* AND vs OR for searching records + * In contrast to the search for guest showing results matching any of keywords given, searching records shows results matching all keywords. This is to allow for more targeted search such as filtering both date and room at the same time to only show records of a particular room at a particular time. This increases the utility of the function in terms of contact tracing. +* Consistency + * The `ResidencyBook` of past records in `AddressBook` mirrors the storage of guests, rooms and current residencies. A `FilteredList` + in `ModelManager` to represent the records also helps maintain the consistency and readability of the code. -------------------------------------------------------------------------------------------------------------------- @@ -257,13 +306,15 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts -* prefer desktop apps over other types +* hotel receptionist +* has a need to manage a significant number of guests and rooms +* needs a solution for contact tracing within their hotel +* prefers desktop apps over other types * can type fast * prefers typing to mouse interactions * is reasonably comfortable using CLI apps -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: manage both guests and rooms faster than a typical mouse/GUI driven app ### User stories @@ -273,50 +324,35 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli | Priority | As a …​ | I want to …​ | So that I can…​ | | -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | | `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | - -*{More to be added}* +| `* * *` | user | add a guest as a contact | check them into rooms | +| `* * *` | user | check guests into rooms | admit them into our hotel | +| `* * *` | user | check guests out of rooms | free up the room and have their information in the archive | +| `* * *` | user | search for vacant rooms | assign guests to a vacant room | +| `* * *` | user | delete guests | remove them if the wrong details are entered | +| `* * *` | user | list all guests and rooms | check all the statuses | +| `*` | user with many guests in the address book | sort guests by name | locate a guest easily | +| `* *` | user | search guests by their name | find a guest's details easily | +| `* * *` | user who has to track past records of guests | perform queries on past data | check records of past guests and details of their stay | +| `* *` | user | add rooms with specified tags | keep track of different types of rooms in my hotel | -### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +### Use Cases -**Use case: Delete a person** - -**MSS** - -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person - - Use case ends. - -**Extensions** - -* 2a. The list is empty. - - Use case ends. - -* 3a. The given index is invalid. - - * 3a1. AddressBook shows an error message. - - Use case resumes at step 2. - -*{More to be added}* +Refer to [_Use Cases_](UseCases.md). ### Non-Functional Requirements -1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -3. 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. +1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. +2. 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. +3. Should work without being connected to the Internet. +4. Should be able to use most basic commands within a day of usage. +5. User should be able to identify key information on the GUI quickly using colour coded text. +6. The system should respond within two seconds of a command input. +7. The product is not required to handle the printing of reports. +8. The rooms show what type of rooms they are, such as First Class or Standard etc. +9. Guests have tags assigned to them to better identify them. +10. The data integrity of the application is preserved in the event where the application closes unexpectedly. -*{More to be added}* ### Glossary @@ -325,7 +361,7 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Instructions for manual testing** +## **Appendix: Instructions for Manual Testing** Given below are instructions to test the app manually. @@ -342,36 +378,222 @@ testers are expected to do more *exploratory* testing. 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. -1. Saving window preferences +2. Saving window preferences 1. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
+ 2. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ - -### Deleting a person +### Guests -1. Deleting a person while all persons are being shown +1. Adding a guest while all guests are being shown - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 1. Prerequisites: List all guests using the `list guests` command. Multiple guests in the list. - 1. 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. + 2. Test case: `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 id/S98765432G`
+ Expected: A new person object is added to the list of guests, with the name John Doe, and the person's details as described by the test case input. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + 3. Test case: `add n/Wilburrito`
+ Expected: No new guest is added. This is to be expected as the mandatory fields for `add` are not fulfilled. An error message should appear with the correct command format that one should be following. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 4. Other incorrect add commands to try: `add`, `add wilburrito`
Expected: Similar to previous. -1. _{ more test cases …​ }_ +2. Editing a guest's details + + 1. List all guests using the `list guests` command. Multiple guests in the list. + + 2. Test case: `edit 1 n/Wilburrito`
+ Expected: The first guest in the list of guests will have their name edited to `Wilburrito`, with a success message reiterating the edited details of the person. + + 3. Test case: `edit 2 n/Wilburger id/S9999999X`
+ Expected: The second person in the list of guests will have their name edited to `Wilburger` as well as their id edited to `S9999999X`. + + 4. Test case: `edit 0 n/Wilburroni`
+ Expected: No guests in the list will be edited, and an error will appear saying `Invalid Command Format!` due to the index being invalid. + + 5. Other incorrect edit commands to try: `edit`, `edit x` where `x` is an index that is larger than the largest index of the guest list.
+ Expected: No guests in the list will be edited, and an error will appear saying `Invalid Command Format!`. + +3. Checking in a guest into a room + + 1. Prerequisites: + 1. List all Person objects using the `list guests` command. Multiple guests in the list. + 2. List all the Room objects using the `list rooms` room. Multiple rooms in the list. + + 2. Test case: `checkin 001 g/1`
+ Expected: The first person in the list of guests gets checked into Room 001, with the success message: `Room Checked In: 001`. + + 3. Test case: `checkin 002 g/2 g/3`
+ Expected: The second and third person in the list of guests gets checked into Room 002, with the success message: `Room Checked In: 002`. + + 4. Test case: `checkin 000 g/4 g/5`
+ Expected: The fourth and fifth person in the list of guests **does not** get checked into Room 000, because Room 000 does not exist, as it is an invalid room number. + + 5. Other incorrect checkin commands to try: `checkin`, `checkin 1000 g/1`, `checkin g/1`
+ Expected: Similar to previous. + +4. Checking out a guests from a room + + 1. Prerequisites: + 1. List all Person objects using the `list guests` command. Multiple guests in the list. + 2. List all the Room objects using the `list rooms` room. Multiple rooms in the list. + 3. Make sure that at least 1 guest is checked into any Room 001. + + 2. Test case: `checkout 001`
+ Expected: The guest in Room 001 will be checked out with the message `Room Checked Out: 001` being shown. The room's occupancy status should change from `Occupied` to `Vacant`. + + 3. Test case: `checkout 1000`
+ Expected: An error saying `The room index provided is invalid. Index should be the one that is displayed in the Room panels below`. + + 4. Other incorrect commands to try: `checkout`, `checkout x`, where `x` is an index greater than the largest index in the current Room list.
+ Expected: For `checkout`, an error `Invalid command format!` will be shown. For `checkout x`, There will be the same error as described in 6iii. + +5. Searching for guests + + 1. List all guests using the `list guests` command. Multiple guests in the list. Make sure that there is a guest named `Wilburrito` and a guest named `Bernice` by either editing an existing guest or adding a new one, and also that there is no guest named `zzzzzzzz`. Make sure to do this before testing each of the test cases below. + + 2. Test case: `guest wilburrito`
+ Expected: The list will show any matches to the name `wilburrito`. If you followed step 4i, this will return at least 1 guest in the guest list. + + 3. Test case: `guest wilburrito bernice`
+ Expected: The list will show any matches to the name `wilburrito` and `bernice`. If you followed step 4i, this will return at least 2 guests in the guest list. + + 4. Test case: `guest zzzzzzzz`
+ Expected: The list will show any matches to the name `zzzzzzzz`. If you followed step 4i, this will return 0 guests in the guest list. + + 5. Incorrect commands to try: `guest`. + +### Rooms + +1. Adding rooms + + 1. List all rooms using the `list rooms` command. Multiple rooms in the list, not exceeding 900 rooms. + + 2. Test case: `addroom 1 t/luxury`
+ Expected: A room will be added to the end of the Room list, and it will appear with the tag `luxury`. + + 3. Test case: `addroom 3 t/special`
+ Expected: 3 rooms wll be added to the end of the Room list, and they will appear with the tag `special`. + + 4. Test case: `addroom 1000 t/shouldnotwork`
+ Expected: No rooms will be added, and an error will be shown, saying `Adding 1000 more room(s) would exceed the maximum 999 rooms allowed`. + + 5. Other incorrect commands to try: `addroom`, `addroom 1`, `addroom x` where `x` will cause the number of rooms to exceed 1000. + Expected: `addroom`, `addroom 1` will cause an error to be shown, saying `Invalid command format`. `addroom x` will cause the same error to be shown as described by 5iv. + +2. Searching for rooms by room number + + 1. List all rooms using the `list rooms` command. Multiple rooms in the list (at least 2 but not more than 900). Make sure to use this command each time before trying a new test case. + + 2. Test case: `room 001`
+ Expected: The room list should now only show `001`. + + 3. Test case: `room 001 002`
+ Expected: The room list should now only show `001` and `002`. + + 4. Test case: `room 901`
+ Expected: The room list should show no rooms, as there were only 900 rooms in the initial room list. + + 5. Test case: `room 1000`
+ Expected: An error `Invalid command format!` will be shown, and the specified room will not appear as it is not possible for it to exist. + + 6. Other invalid commands to try: `room`, `room 000`. + Expected: An error `Invalid command format!` will be shown. Depending on the command input, a brief description of why the command is invalid may be provided. + +3. Listing all rooms + + 1. Test case: `list rooms`
+ Expected: All rooms are displayed in the Rooms panel. + +4. Listing all occupied rooms + + 1. Test case: `list rooms occupied`
+ Expected: All occupied rooms are displayed in the Rooms panel. + +5. Listing all vacant rooms + + 1. Test case: `list rooms vacant`
+ Expected: All vacant rooms are displayed in the Rooms panel. + +### Records + +1. Listing all records + + 1. Test case: `list records`
+ Expected: All past records are displayed in the History panel, sorted from most recent record at the top. + + +2. Searching for records + + 1. List all records using the `list records` command. Multiple records in the list (at least 2). Make sure to use this command each time before trying a new test case. + + 2. Test case: `record Alex`
+ Expected: All records with the keyword `Alex` are displayed in the History panel. + + 3. Test case: `record 001`
+ Expected: All records with the keyword `001` are displayed in the History panel + + 4. Test case: `record 2021-11-01`
+ Expected: All records with the date 2021-11-01(both checkin and checkout) are displayed in the History panel. + + 5. Test case: `record Alex 001`
+ Expected: ALl records with both keywords `Alex` and `001` are displayed in the History panel. + + 6. Invalid command to try: `record`. + Expected: An error `Invalid command format!` will be shown. ### Saving data -1. Dealing with missing/corrupted data files +1. Dealing with corrupted data files + + 1. Locate the data saved for Trace2Gather in the JSON file `[JAR file location]/data/trace2gather.json`. + + 2. Open the file and remove some braces to invalidate the data format of the json file. + + 3. Re-run the application. - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ + 4. Expected: Trace2gather runs, showing a GUI with no data. Upon a command that writes to the data file such as adding a room or guest, the old invalid data file is flushed out and replaced by the new one. + +2. Dealing with missing data file + + 1. Remove the data file saved for Trace2Gather in the directory `[JAR file location]/data`. + + 2. Re-run the application. + + 3. Expected: Trace2gather creates a sample data file and runs, showing the GUI with the sample data. + +-------------------------------------------------------------------------------------------------------------------- -1. _{ more test cases …​ }_ +## **Appendix: Effort** + +#### Summary +In this project, we experienced challenges when implementing our backend, frontend, editing documentation, and fixing bugs. +

+In the backend, we had to build on the existing implementation and introduced our own data structures to prevent cyclic-dependencies. +

+On the frontend, we had to match the specifications as much as possible whilst also ensuring that our new features not only worked well but also stylistically was coherent to our product. +

+In the documentation, we had to edit many of the diagrams and their accompanying explanations to account for the changes in our application as compared to the original AB3. +

+#### Backend +The naive implementation would have been for Room objects to contain a set of guests (Persons), and once a room is checked out, all the room's information is moved into a list containing all historical records. +The Person objects would also contain the Room that they are checked into. +However, Person objects are immutable in AB3, and Room objects were made immutable to match. This would make it difficult for both objects to store references to each other, especially since Person objects can be edited frequently. +

+Our solution was to create an association class, the Residency class, that stores pointers to both the Room and guests. This way, when a guest is edited, we use the guest's information to retrieve the +Residency object that is keeping track of all the rooms that has this same guest inside and all the historical records that have this same guest inside and update the guest to reflect the edited guest's information. +

+#### Frontend +While we reused many of the original AB3's code extensively, our project was harder due to having 3 different lists as compared to the AB3 which originally only had 1 list. +This was compounded by the fact that we had dependencies between these 3 entities, and we had to make sure that the updates in the backend are reflected in the frontend. +

+We also made an effort to ensure that stylistically, the new additions were different from the original AB3 but still helped to fit into the overall style of the project. +

+#### Documentation +As our implementation included new data structures and new commands to interact with those data structures, we had to modify the documentation extensively to reflect these new changes. +This included creating new diagrams and adding new explanations for our features. +

+Furthermore, our application also removed some commands from the original AB3, most notably the delete command. +Since this command was used as an example along with its accompanying sequence diagram, we had to modify that entire section and replace the sequence diagram too. diff --git a/docs/Documentation.md b/docs/Documentation.md index 3e68ea364e7..e8549e06f69 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -1,6 +1,6 @@ --- layout: page -title: Documentation guide +title: Documentation Guide --- **Setting up and maintaining the project website:** diff --git a/docs/Logging.md b/docs/Logging.md index 5e4fb9bc217..e9da277b9e5 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -1,6 +1,6 @@ --- layout: page -title: Logging guide +title: Logging Guide --- * We are using `java.util.logging` package for logging. diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 275445bd551..c28a1fd7636 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -1,6 +1,6 @@ --- layout: page -title: Setting up and getting started +title: Setting Up and Getting Started --- * Table of Contents diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..ab9606c5960 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -1,6 +1,6 @@ --- layout: page -title: Testing guide +title: Testing Guide --- * Table of Contents diff --git a/docs/UseCases.md b/docs/UseCases.md new file mode 100644 index 00000000000..718672f81ea --- /dev/null +++ b/docs/UseCases.md @@ -0,0 +1,242 @@ +--- +layout: page +title: Use Cases +--- + +This page contains the use cases in our project. The sections below have been segmented to major sections where each +use case is classified under to allow for easier navigation. +

+For all use cases below, the **System** is the `Trace2Gather` and the **Actor** is the `user`, unless specified otherwise. + + +* Table of Contents +{:toc} + +-------------------------------------------------------------------------------------------------------------------- + +## Guest + +### Use case: Add a guest +**MSS** + +1. User requests to add a guest and inputs the necessary parameters. +2. Trace2Gather adds the guest with the input details. +3. Trace2Gather displays a message to the user indicating that the guest has been added. + + Use case ends. + +**Extensions** + +* 1a. There are missing parameters or input is not in the correct format. + * 1a1. Trace2Gather displays a message indicating to the user the required parameters. + * 1a2. User inputs the parameters again until all required parameters are input. + + Use case resumes at step 2. + +

+ +### Use case: Edit a guest +**MSS** +1. User requests to list guests. +2. Trace2Gather shows a list of guests. +3. User requests to edit a guest at a specific index in the list. +4. Trace2Gather edits the guest. + + Use case ends. + +**Extensions** +* 2a. The list is empty. + + Use case ends. + +* 2b. The given index is invalid. + * 2b1. Trace2Gather displays a message indicating to the user the required format. + * 2b2. User edits the index to the correct index. + + Use case resumes at step 3. + +* 2c. The input command is not in the correct format. + * 2c1. Trace2Gather displays a message indicating to the user the required format. + * 2c2. User edits the input to the correct format. + + Use case resumes at step 3. + +

+ +### Use case: Retrieve all guests +**MSS** + +1. User requests to retrieve all the entries of guests. +2. User is able to view all the entries of guests. + + Use case ends. + +### Use case: Search for guest(s) by name +**MSS** + +1. User requests to find a specific guest using the name of the guest. +2. User is able to view all guests with the same name. + + Use case ends. + +**Extensions** +* 1a. The list containing guests with the same name is empty. + + Use case ends. + +

+ + +-------------------------------------------------------------------------------------------------------------------- + +## Room + +### Use case: Add room(s) +**MSS** + +1. User requests to add a room or multiple rooms along with their tags to indicate properties about the room. +2. Trace2Gather adds the room(s) with the tagged properties details. +3. Trace2Gather displays a message to the user indicating that the room(s) has been added. + + Use case ends. + +**Extensions** + +* 1a. There are missing parameters or input is not in the correct format. + * 1a1. Trace2Gather displays a message indicating to the user the required parameters. + * 1a2. User inputs the parameters again until all required parameters are input. + + Use case resumes at step 2. + +

+ +### Use case: Search for room(s) +**MSS** + +1. User requests to find a specific room / some rooms using the room number. +2. User is able to view all rooms as specified. + + Use case ends. + +**Extensions** +* 1a. The list containing room(s) with desired room number(s) is empty. + + Use case ends. + +

+ +### Use case: Retrieve all occupied rooms +**MSS** + +1. User requests to retrieve all occupied rooms. +2. User is able to view all the entries of occupied rooms. + + Use case ends. + +**Extensions** +* 1a. The list containing all occupied rooms is empty. + + Use case ends. + +

+ +### Use case: Retrieve all vacant rooms +**MSS** + +1. User requests to retrieve all vacant rooms. +2. User is able to view all vacant rooms. + + Use case ends. + +**Extensions** +* 1a. The list containing all vacant rooms is empty. + + Use case ends. + +

+ +### Use case: Retrieve all rooms +**MSS** + +1. User requests to retrieve all the entries of rooms. +2. User is able to view all the entries of rooms. + + Use case ends. + +

+ +### Use case: Check-in guest(s) to a room +**MSS** + +1. User requests check-in guest(s) into a room using guest list index. +2. Trace2Gather adds the guest(s) into the corresponding room. +3. Trace2Gather displays a message to the user indicating that the room(s) has been added. +4. Occupancy status of specified room is updated, and the guest(s) are shown in the room. + + Use case ends. + +**Extensions** + +* 1a. Guest index is not valid + * 1a1. Trace2Gather displays a message indicating to the user that the command is invalid. + * 1a2. User changes the index to be correct. + + Use case resumes at step 2. + +* 1b. Guest(s) is already in another room. + * 1a1. Trace2Gather displays a message indicating to the user the guests that already have a room. + + Use case ends. + +

+ +### Use case: Check-out room +**MSS** + +1. User requests to check-out guest(s) current in a room. +2. Room and its guests are added into History list. +3. Trace2Gather removes the guest(s) from room. +4. Trace2Gather displays a message to the user indicating that the room(s) has been checked-out. +5. Occupancy status of specified room is updated. + + Use case ends. + +**Extensions** + +* 1a. Room is empty. + * 1a1. Trace2Gather displays a message indicating to the user that the room is vacant. + Use case ends. + +

+ +-------------------------------------------------------------------------------------------------------------------- + +## History + +### Use case: Retrieve all history records +**MSS** + +1. User requests to retrieve all history record entries. +2. User is able to view all history record entries. + + Use case ends. + +

+ +### Use case: Search for specific history record +**MSS** + +1. User requests to find a specific history record using either room number or guest name. +2. User is able to view all history records as specified. + + Use case ends. + +**Extensions** +* 1a. The list containing history records with desired history record(s) is empty. + + Use case ends. + +

+ +-------------------------------------------------------------------------------------------------------------------- + diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..ee25300b530 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,190 +3,442 @@ layout: page title: User Guide --- -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. +Welcome to the Trace2Gather User Guide. Trace2Gather is a **desktop app for managing hotel rooms and guests, optimised for use via a Command Line Interface (CLI)**, while still having the benefits of a Graphical User Interface (GUI). +Our application aims to complement existing contact tracing efforts, while also helping you to manage your hotel guests fast. +

+The purpose of this user guide is to help you explore Trace2Gather's many features and how to use them. +

+If you require assistance using this guide, please feel free to visit the [Navigation Guide](#navigation-guide) for more information. + +
+ +
Table Of Contents
* Table of Contents {:toc} --------------------------------------------------------------------------------------------------------------------- +
+ +## Navigation Guide + +Before we guide you through our application's features, please visit the [Quick Start](#quick-start) section to ensure our application can run on your device. +

+After you are done setting up, check out the [Features](#features) section to explore the various features our application offers, and how to use them. +

+If you would like a quick overview of all available commands, check out the [Command Summary](#command-summary). +

+If you have any questions, they may be answered in the [Frequently Asked Questions (FAQ)](#frequently-asked-questions-faq) section. -## Quick start +
+ +## Quick Start +This section will help you get our application running on your device, step by step. 1. Ensure you have Java `11` or above installed in your Computer. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +2. Download the latest `trace2gather.jar` from [here](https://github.com/AY2122S1-CS2103T-T13-3/tp/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +3. Copy the file to the folder you want to use as the _home folder_ for Trace2Gather. -1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+4. Double-click the file to start the application. The GUI below should appear in a few seconds, and will look like the screenshot below. Do note that the application contains some sample data for your convenience.
![Ui](images/Ui.png) -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: +5. Type a command in the command box (shown in the screenshot below) and press Enter to execute it. For example, typing **`list guests`** and pressing Enter will show the UI below.
+ ![CommandBox](images/NavGuideCommandBoxImage.png) + Some example commands you can try are the following: + + * **`list guests`** : Lists all guests. - * **`list`** : Lists all contacts. + * **`addroom 5 t/typeA`** : Adds 5 rooms of type A to the room list. - * **`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. + * **`add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 id/S98765432H`** : Adds a contact named `John Doe` to the Trace2Gather. - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. + * **`checkin 5 g/1`**: Checks in the 1st guest into the 5th room in the room list. - * **`clear`** : Deletes all contacts. + * **`clear`** : Deletes all rooms, guests and past records. * **`exit`** : Exits the app. -1. Refer to the [Features](#features) below for details of each command. --------------------------------------------------------------------------------------------------------------------- +6. Refer to the [Features](#features) section below for details of each command. + +
## Features +This section showcases all our application's features. +Each feature has its own sub-section, with formats and examples for clarity. +
**:information_source: Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +* All commands are in lower case,
+ For example, commands are used as `add`, `record`, `guest`, `addroom`, etc. + +* Words in `UPPER_CASE` are the parameters to be supplied by you.
+ For example, in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. * Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. + For example: `n/NAME [t/TAG]` can be used as `n/John Doe t/Quarantine` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times.
- e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. +* Items with `…`​ indicates that there can be additional parameters of the same type.
+ For example: `[t/TAG]…​` can be used as `t/Quarantine t/SeafoodAllergy` or just `t/Quarantine`. * 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. + For example, if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. -* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. +* If a parameter is expected only once in the command, yet it is specified multiple times, only the last occurrence of the parameter will be taken.
+ For example, if you specify `p/11111111 p/22222222`, only `p/22222222` will be used. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`. +* Extraneous parameters for commands that do not take in parameters (such as `help`, `exit` and `clear`) will be ignored.
+ For example, if the command specifies `help 123`, it will be interpreted as `help`. +* If you have a small screen, please use the application in full screen mode by clicking on the square icon at the top right-hand corner of the window. + +* For devices with smaller screens, some information about guests, rooms, or residencies may be cut off with an ellipses.
-### Viewing help : `help` -Shows a message explaning how to access the help page. +
+ +#### Viewing help : `help` + +Shows a message explaining how to access the [help page](https://ay2122s1-cs2103t-t13-3.github.io/tp/UserGuide.html). ![help message](images/helpMessage.png) Format: `help` +### Guests -### Adding a person: `add` +#### Adding a guest: `add` -Adds a person to the address book. +Adds a guest to Trace2Gather. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS id/ID [t/TAG]…​` -
:bulb: **Tip:** -A person can have any number of tags (including 0) +Acceptable format for keywords:
+1. Names: No special characters, but spaces are allowed. No longer than 50 characters. +2. Phone Number: Digits only and at least 3 digits long. +3. Email: Must follow the format of xxx@yyy.zzz.
+4. Address: Special characters such as `#` are allowed. Field must not be blank. +5. Id: Accommodates for international guests who may have longer identification numbers and/or special characters. It must not be empty and it must be no longer than 50 characters. It is case-insensitive. +6. Tags: No whitespaces within a tag. + +
:bulb: **Tips:**
+1. A guest can have any number of tags (including no tags at all).
+2. Two guests who have identical IDs are considered identical.
Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 id/S98765432G` +* `add n/Betsy Crowe t/Quarantine e/betsycrowe@example.com a/Crowe Lane id/S98765431G p/1234567 t/SeafoodAllergy` -### Listing all persons : `list` -Shows a list of all persons in the address book. +
-Format: `list` +#### Listing all guests : `list guests` -### Editing a person : `edit` +Shows a list of all guests in Trace2Gather. If you were searching for a guest earlier using the `guest` command, using `list guests` would yield you the full list of guests, and it would be displayed in the `Guests` panel. This is illustrated in the screenshot below after the execution of the `list guests` command.
-Edits an existing person in the address book. +![listguests](images/ListGuestsAfter.png) -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Format: `list guests` -* 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, …​ + +
+ +#### Editing a guest : `edit` + +Edits an existing guest in Trace2Gather. + +Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [id/ID] [t/TAG]…​` +* Edits the guest at the specified `INDEX`. The index refers to the index number shown in the displayed guest list. The index **must be a positive integer** 1, 2, 3, …​ * At least one of the optional fields must be provided. * Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +* When editing tags, the existing tags of the guest will be removed, That is, the addition of tags is not cumulative. +* You can remove all the guest’s tags by typing `t/` without + specifying any tags after it. + +Acceptable format for keywords: +1. Names: No special characters, but spaces are allowed. No longer than 50 characters. +2. Phone Number: Digits only, and at least 3 digits long. +3. Email: Must follow the format of xxx@yyy.zzz.
+4. Address: Special characters like `#` are allowed for address purposes, must not be blank. +5. Id: Accommodates for international guests who may have longer identification numbers and/or special characters. Must not be an empty string, and there is a 50-character limit on the length. +6. Tags: No whitespaces within a tag. + 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. +* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st guest to be `91234567` and `johndoe@example.com` respectively. +* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd guest to be `Betsy Crower` and clears all existing tags. + -### Locating persons by name: `find` +
-Finds persons whose names contain any of the given keywords. +#### Locating guests by name: `guest` -Format: `find KEYWORD [MORE_KEYWORDS]` +Find guests whose names contain any of the given keywords. -* 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` +Format: `guest KEYWORD [MORE_KEYWORDS]` + +* The search is case-insensitive. For example, `hans` will return you `Hans`. +* The order of the keywords does not matter. For example, `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 matching at least one keyword will be returned (i.e. `OR` search). + For example, `Hans Bo` will return `Hans Gruber`, `Bo Yang`. + +
:exclamation: **Caution:** + Only full words will be matched, For example, searching `Han` will not return you `Hans`. +
Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +* `guest John` returns `john` and `John Doe` +* `guest alex david` returns `Alex Yeoh`, `David Li`
+ +The image below is an example of how part of the application interface may look when the command `guest alex david` is executed. + +![guestsearch](images/Guestalexdavid2.jpg) + -### Deleting a person : `delete` +
-Deletes the specified person from the address book. +### Rooms -Format: `delete INDEX` +#### Adding rooms : `addroom` -* 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, …​ +Adds the specified number of rooms with the specified tag(s). + +Format: `addroom NUMBER_OF_ROOMS t/tag [t/tag]...` + +* Adds the specified `NUMBER_OF_ROOMS` of type `tag` to the list of rooms. +* The specified number of rooms **must be a positive integer**. That is, 1, 2, 3, …​ +* The full list of rooms will be shown after rooms have been added. +* Note: There should be no whitespace within a tag. + +
:bulb: **Tip:** +A room can have one or more tags. +
+ +
:exclamation: **Caution:** +You can only add up to 999 rooms. +
Examples: -* `list` followed by `delete 2` deletes the 2nd person in the address book. -* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. +* `addroom 5 t/typeA` +* `addroom 10 t/typeB t/reserved` + + +
+ +#### Checking into a room : `checkin` + +Checks in guest(s) into a room. + +Format: `checkin ROOM_INDEX g/GUEST_INDEX [g/GUEST_INDEX]...` + +
:bulb: **Tips:**
+1. A room can have more than one guest.
+2. If you cannot check guests into a room, check that the room index and guest index(es) are the ones you can see on the panels. Otherwise, use the command `list rooms` and `list guests` to show all rooms and guests.
+
+ +
:exclamation: **Caution:**
+1. You cannot check in the same guest into multiple rooms.
+2. Advanced users can edit the JSON file directly such that a guest can be checked in and checked out at the same time. This is not the intended use of the application. +
+ +The indices refer to the index numbers shown in the numbered guest/room list. + +Examples: +* `checkin 5 g/1` +* `checkin 1 g/2` + +The image below is an example of how part of the application interface looks when the command `checkin 3 g/2 g/3 g/6` is executed. +![checkin3](images/Checkin3.png) + + +
+ +#### Checking out of a room : `checkout` + +Checks out the guest(s) from a room. + +Format: `checkout ROOM_INDEX` + +The room index refers to the index number shown in the displayed room list. -### Clearing all entries : `clear` +Example: +* `checkout 5` +* `checkout 7` -Clears all entries from the address book. +The image below is an example of how part of the application interface looks when the command `checkout 4` is executed. +![checkout4](images/Checkout4.png) + + +
+ +#### Locating a specific room : `room` + +Shows a list of rooms that match the room number(s) provided. Room numbers should have 3 digits. + +Format: `room ROOM_NUMBER [MORE_ROOM_NUMBERS]` + +Example: +* `room 005 010` +* `room 003` + +The image below is an example of how part of the application interface looks when the command `room 001 002` is executed. +![room001002](images/Room001002.png) + + +
+ +#### Listing all rooms : `list rooms` + +Shows the list of all rooms in Trace2Gather. If you were searching for a room using the `room` command earlier, using `list rooms` would yield you the full list of rooms, and it would be displayed in the `Rooms` panel. This is illustrated in the screenshot below after the execution of the `list rooms` command.
+ +![ListRoomsCommand](images/ListRooms.png) + +Format: `list rooms` + + +
+ +#### Listing all occupied rooms : `list rooms occupied` + +Shows the list of all rooms that are occupied in Trace2Gather. The image below illustrates a possible example of what you could see after executing the command. Only Room 002 was occupied, so `list rooms occupied` returns only Room 002, leaving out the rest of the vacant rooms. + +![ListRoomsOccupiedCommand](images/ListRoomsOccupied.png) + + +Format: `list rooms occupied` + + +
+ +#### Listing all vacant rooms : `list rooms vacant` + +Shows the list of all rooms that are vacant in Trace2Gather. The image below illustrates a possible example of what you could see after executing the command. Room 002 was occupied, so `list rooms vacant` does not display Room 002, but displays all other vacant rooms. + +![ListRoomsVacantCommand](images/ListRoomsVacant.png) + +Format: `list rooms vacant` + + +
+ +### Records + +#### Listing all records : `list records` + +Shows the list of past residencies in Trace2Gather. If you were searching for a record earlier using the `record` command, using `list records` would yield you the full list of records, and it would be displayed in the `History` panel. This is illustrated in the screenshot below after the execution of the `list records` command.
+ +![ListRecordsCommand](images/ListRecords.png) + +Format: `list records` + + +
+ +#### Locating specific records: `record` + +Shows the records that match all the keywords provided. Case-insensitive, and at least 1 keyword must be entered.
+ +Format: `record KEYWORD [MORE KEYWORDS]... ` + +The following fields are what you can use to search for a record: +1. Guest Name +2. Guest Id +3. Guest Phone Number +4. Guest Address +5. Guest Email +6. Guest Tags +7. Room Number +8. Checkin Time +9. Checkout Time + +Acceptable formats for keywords:
+1. Dates of stay: YYYY-MM-DD. +2. Names: No special characters, but spaces are allowed. +3. Room Numbers: must be in its 3-digit format. For example, 001, 233, 999.
+ +Example:
+`record Alex` shows the residencies Alex had in the past.
+`record 001` shows the residencies Room 001 had in the past.
+`record Alex 001` shows the residencies that involve Alex staying in Room 001 in the past.
+`record Alex Bernice` shows the residencies Alex and Bernice had together in the past.
+`record 2021-10-31` shows the past residences that include the specified date (both checkin and checkout included). + + +
+ +### Database / Storage + +#### Clearing all entries : `clear` + +Clears all entries from Trace2Gather. Format: `clear` -### Exiting the program : `exit` +
:exclamation: **Caution:** +Using this command will clear your JSON file, which means that all your room and guest objects will be erased. +
+ +#### Exiting the program : `exit` Exits the program. Format: `exit` -### Saving the data +#### Saving the data -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +Trace2Gather data is saved in the hard disk automatically after any command that changes the data is executed. There is no need to save manually. -### Editing the data file +#### Editing the data file -AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +Trace2Gather data is saved as a JSON file `[JAR file location]/data/trace2gather.json`. Advanced users are welcome to update the data directly by editing the JSON file.
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. +If your changes to the JSON file causes the data within the file to be invalid, Trace2Gather will discard all the data in the JSON file and start with an empty data file at the next run.
-### Archiving data files `[coming in v2.0]` +
-_Details coming soon ..._ +## Frequently Asked Questions (FAQ) +We understand that you might have questions regarding our application. Below are some common questions we have been asked. --------------------------------------------------------------------------------------------------------------------- +**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 Trace2Gather home folder. -## FAQ +**Q**: Why does my edit / checkin / checkout command not work?
+**A**: The indices required by these commands are the ones from the numbered list seen in the GUI. You can try using one or more of the `list` commands to show all available data and make sure you have the correct indices. + +**Q**: Why does our application's logo look very similar to TraceTogether's logo?
+**A**: Our application's purpose was driven with the intention to complement TraceTogether and thus we decided to be faithful to the logo used. The difference in colour scheme is a nod to the contributors who made AB3, the original source code we based our application off from, as AB3's logo was brown in colour. + +**Q**: How do I know if my application is not responding?
+**A**: Please try to use your computer's task manager, such as Window's Task Manager, to check that the application is running. + +**Q**: I would like to use your application's codebase to develop my product. Is that alright with you?
+**A**: Yes, please feel free to use our code base! Just remember that our application was built off AB3's code base found [here](https://github.com/nus-cs2103-AY2122S1/tp). Thus, please do remember to acknowledge the developers behind the code base. -**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 AddressBook home folder. --------------------------------------------------------------------------------------------------------------------- +
-## Command summary +## Command Summary Action | Format, Examples --------|------------------ -**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` +**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS id/ID [t/TAG]…​`
Example: `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 id/S9943233F t/friend t/colleague` +**Addroom** | `addroom NUMBER_OF_ROOMS t/tag [t/tag]...`
Example: `addroom 5 t/typeA` **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` +**Checkin** | `checkin ROOM_INDEX g/GUEST_INDEX [g/GUEST_INDEX]...`
Example: `checkin 5 g/1` +**Checkout** | `checkout ROOM_INDEX`
Example: `checkout 4` +**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [id/ID] [t/TAG]…​`
Example: `edit 2 n/James Lee e/jameslee@example.com` +**Exit** | `exit` +**Guest** | `guest KEYWORD [MORE_KEYWORDS]`
Example: `guest James Jake` +**List** | `list guests`, `list records`, `list rooms`, `list rooms occupied`, `list rooms vacant` **Help** | `help` +**Record** | `record KEYWORD_ONE...`
Example: `record Alex`, `record 001` +**Room** | `room ROOM_NUMBER [MORE_ROOM_NUMBERS]`
Example: `room 001 002` diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..05a1b524946 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "Trace2Gather" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2122S1-CS2103T-T13-3/tp/" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..9dc8dcc1230 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "Trace2Gather"; font-size: 32px; } } diff --git a/docs/_sass/minima/custom-styles.scss b/docs/_sass/minima/custom-styles.scss index a992115a70f..27bc55f2f51 100644 --- a/docs/_sass/minima/custom-styles.scss +++ b/docs/_sass/minima/custom-styles.scss @@ -31,4 +31,3 @@ h2, h3, h4, h5, h6 { @include alert-variant(color-level($value, $alert-bg-level), color-level($value, $alert-border-level), color-level($value, $alert-color-level)); } } - diff --git a/docs/diagrams/AddressBookSubset.puml b/docs/diagrams/AddressBookSubset.puml new file mode 100644 index 00000000000..2a90bf7f057 --- /dev/null +++ b/docs/diagrams/AddressBookSubset.puml @@ -0,0 +1,34 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Model <>{ +Interface ReadOnlyAddressBook <> +Interface ReadOnlyResidencyBook <> +Interface Model <> +Class AddressBook +Class ReadOnlyAddressBook +Class Model +Class ModelManager +Class ResidencyBook +Class Residency + +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Model + +AddressBook .up.|> ReadOnlyAddressBook + +ModelManager .up.|> Model +Model .left.> ReadOnlyAddressBook +ModelManager -left-> "1" AddressBook + + +AddressBook *-left-> "2" ReadOnlyResidencyBook +ResidencyBook .up.|> ReadOnlyResidencyBook +ResidencyBook --> "~*" Residency + +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..59e77fd8440 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -7,13 +7,13 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "guest Alex" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("guest Alex") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : updateFilteredPersonList(predicate) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 5731f9cbaa1..c454b22794d 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -6,11 +6,20 @@ skinparam classBackgroundColor MODEL_COLOR AddressBook *-right-> "1" UniquePersonList AddressBook *-right-> "1" UniqueTagList +AddressBook *-down-> "1" RoomList +AddressBook *-down-> "1" ResidencyBook UniqueTagList -[hidden]down- UniquePersonList UniqueTagList -[hidden]down- UniquePersonList UniqueTagList *-right-> "*" Tag UniquePersonList -right-> Person +RoomList -down-> Room + +ResidencyBook -down-> "*" Residency +ResidencyBook -down-> Room +ResidencyBook -down-> Person +Residency -down-> Person +Residency -down-> Room Person -up-> "*" Tag @@ -18,4 +27,7 @@ Person *--> Name Person *--> Phone Person *--> Email Person *--> Address + +Room -down-> RoomNumber +Room -down-> Vacancy @enduml diff --git a/docs/diagrams/FindGuestSequenceDiagram.puml b/docs/diagrams/FindGuestSequenceDiagram.puml new file mode 100644 index 00000000000..04c2917a571 --- /dev/null +++ b/docs/diagrams/FindGuestSequenceDiagram.puml @@ -0,0 +1,69 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindGuestCommandParser" as FindGuestCommandParser LOGIC_COLOR +participant "f:FindGuestCommand" as FindGuestCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("guest Alex") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("guest Alex") +activate AddressBookParser + +create FindGuestCommandParser +AddressBookParser -> FindGuestCommandParser +activate FindGuestCommandParser + +FindGuestCommandParser --> AddressBookParser +deactivate FindGuestCommandParser + +AddressBookParser -> FindGuestCommandParser : parse("Alex") +activate FindGuestCommandParser + +create FindGuestCommand +FindGuestCommandParser -> FindGuestCommand +activate FindGuestCommand + +FindGuestCommand --> FindGuestCommandParser : f +deactivate FindGuestCommand + +FindGuestCommandParser --> AddressBookParser : f +deactivate FindGuestCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +FindGuestCommandParser -[hidden]-> AddressBookParser +destroy FindGuestCommandParser + +AddressBookParser --> LogicManager : f +deactivate AddressBookParser + +LogicManager -> FindGuestCommand : execute() +activate FindGuestCommand + +FindGuestCommand -> Model : updateFilteredPersonList(predicate) +activate Model + +Model --> FindGuestCommand +deactivate Model + +create CommandResult +FindGuestCommand -> CommandResult +activate CommandResult + +CommandResult --> FindGuestCommand +deactivate CommandResult + +FindGuestCommand --> LogicManager : result +deactivate FindGuestCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ListRoomsByVacancySequenceDiagram.puml b/docs/diagrams/ListRoomsByVacancySequenceDiagram.puml new file mode 100644 index 00000000000..db1c4dd240e --- /dev/null +++ b/docs/diagrams/ListRoomsByVacancySequenceDiagram.puml @@ -0,0 +1,82 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ListCommandParser" as ListCommandParser LOGIC_COLOR +participant "l:ListCommand" as ListCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +participant "<>\nParserUtil" as ParserUtil LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("list rooms vacant") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("list rooms vacant") +activate AddressBookParser + +create ListCommandParser +AddressBookParser -> ListCommandParser +activate ListCommandParser + +ListCommandParser --> AddressBookParser +deactivate ListCommandParser + +AddressBookParser -> ListCommandParser : parse("rooms vacant") +activate ListCommandParser + +ListCommandParser -> ParserUtil : parseListType("rooms") +activate ParserUtil + +ParserUtil --> ListCommandParser +deactivate ParserUtil + +ListCommandParser -> ParserUtil : parseListRoomArgument("vacant") +activate ParserUtil + +ParserUtil --> ListCommandParser +deactivate ParserUtil + +create ListCommand +ListCommandParser -> ListCommand +activate ListCommand + +ListCommand --> ListCommandParser : l +deactivate ListCommand + +ListCommandParser --> AddressBookParser : l +deactivate ListCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ListCommandParser -[hidden]-> AddressBookParser +destroy ListCommandParser + +AddressBookParser --> LogicManager : l +deactivate AddressBookParser + +LogicManager -> ListCommand : execute() +activate ListCommand + +ListCommand -> Model : updateFilteredRoomList(RoomIsVacantPredicate) +activate Model + +Model --> ListCommand +deactivate Model + +create CommandResult +ListCommand -> CommandResult +activate CommandResult + +CommandResult --> ListCommand +deactivate CommandResult + +ListCommand --> LogicManager : result +deactivate ListCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 1122257bd9a..5ad3520aa19 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -7,6 +7,7 @@ skinparam classBackgroundColor MODEL_COLOR Package Model <>{ Interface ReadOnlyAddressBook <> Interface ReadOnlyUserPrefs <> +Interface ReadOnlyResidencyBook <> Interface Model <> Class AddressBook Class ReadOnlyAddressBook @@ -15,7 +16,6 @@ Class ModelManager Class UserPrefs Class ReadOnlyUserPrefs - Class UniquePersonList Class Person Class Address @@ -23,6 +23,14 @@ Class Email Class Name Class Phone Class Tag +Class Nric + +Class RoomList +Class Room +Class RoomNumber +Class Vacancy +Class ResidencyBook +Class Residency } @@ -45,10 +53,28 @@ Person *--> Phone Person *--> Email Person *--> Address Person *--> "*" Tag +Person *--> Nric + +AddressBook *--> "1" RoomList +RoomList --> "~* all" Room +Room *--> RoomNumber +Room *--> Vacancy +Room *--> "*" Person +Room *--> "1..*" Tag + Name -[hidden]right-> Phone Phone -[hidden]right-> Address Address -[hidden]right-> Email +AddressBook *-left-> "2" ReadOnlyResidencyBook +ResidencyBook .up.|> ReadOnlyResidencyBook +ResidencyBook --> "~*" Residency +ResidencyBook --> "~*" Room +ResidencyBook --> "~*" Person +Residency --> "1...*" Person +Residency --> "1" Room + + ModelManager -->"~* filtered" Person @enduml diff --git a/docs/diagrams/RecordCommandSequenceDiagram.puml b/docs/diagrams/RecordCommandSequenceDiagram.puml new file mode 100644 index 00000000000..e7f2a7e443d --- /dev/null +++ b/docs/diagrams/RecordCommandSequenceDiagram.puml @@ -0,0 +1,45 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "r:FindRecordCommand" as FindRecordCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box +[-> LogicManager : execute("record 001") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("record 001") +activate AddressBookParser + +create FindRecordCommand +AddressBookParser -> FindRecordCommand +activate FindRecordCommand + +FindRecordCommand --> AddressBookParser +deactivate FindRecordCommand + +AddressBookParser --> LogicManager : r +deactivate AddressBookParser + +LogicManager -> FindRecordCommand : execute() +activate FindRecordCommand + +FindRecordCommand -> Model : updateFilteredRecordList(predicate) +activate Model + +Model --> FindRecordCommand +deactivate Model + +FindRecordCommand --> LogicManager : result +deactivate FindRecordCommand +FindRecordCommand -[hidden]-> LogicManager : result +destroy FindRecordCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 85ac3ea2dee..720173cccfd 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -19,7 +19,10 @@ Interface AddressBookStorage <> Class JsonAddressBookStorage Class JsonSerializableAddressBook Class JsonAdaptedPerson +Class JsonAdaptedRoom Class JsonAdaptedTag +Class JsonAdaptedResidencyBook +Class JsonAdaptedResidency } } @@ -38,6 +41,10 @@ JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook JsonSerializableAddressBook --> "*" JsonAdaptedPerson +JsonSerializableAddressBook --> "*" JsonAdaptedRoom JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonAdaptedResidencyBook --> "*" JsonAdaptedResidency +JsonSerializableAddressBook --> "2" JsonAdaptedResidencyBook + @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index ecae4876432..d722e2a95ef 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -13,6 +13,10 @@ Class HelpWindow Class ResultDisplay Class PersonListPanel Class PersonCard +Class RoomListPanel +Class RoomCard +Class ResidencyListPanel +Class ResidencyCard Class StatusBarFooter Class CommandBox } @@ -32,11 +36,15 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay +MainWindow *-down-> "1" RoomListPanel MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" ResidencyListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow PersonListPanel -down-> "*" PersonCard +RoomListPanel -down-> "*" RoomCard +ResidencyListPanel -down-> "*" ResidencyCard MainWindow -left-|> UiPart @@ -44,10 +52,15 @@ ResultDisplay --|> UiPart CommandBox --|> UiPart PersonListPanel --|> UiPart PersonCard --|> UiPart +RoomListPanel --|> UiPart +RoomCard --|> UiPart +ResidencyListPanel --|> UiPart +ResidencyCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart PersonCard ..> Model +RoomCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic diff --git a/docs/images/AddressBookSubset.png b/docs/images/AddressBookSubset.png new file mode 100644 index 00000000000..212040f9ca6 Binary files /dev/null and b/docs/images/AddressBookSubset.png differ diff --git a/docs/images/ArchitectureSequenceDiagramForDG.png b/docs/images/ArchitectureSequenceDiagramForDG.png new file mode 100644 index 00000000000..d4cf44d30c3 Binary files /dev/null and b/docs/images/ArchitectureSequenceDiagramForDG.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 1ec62caa2a5..00fa4846612 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/Checkin3.png b/docs/images/Checkin3.png new file mode 100644 index 00000000000..264dd322525 Binary files /dev/null and b/docs/images/Checkin3.png differ diff --git a/docs/images/Checkout4.png b/docs/images/Checkout4.png new file mode 100644 index 00000000000..bc41b937e32 Binary files /dev/null and b/docs/images/Checkout4.png differ diff --git a/docs/images/FindGuestSequenceDiagram.png b/docs/images/FindGuestSequenceDiagram.png new file mode 100644 index 00000000000..b9b06977406 Binary files /dev/null and b/docs/images/FindGuestSequenceDiagram.png differ diff --git a/docs/images/Guestalexdavid1.png b/docs/images/Guestalexdavid1.png new file mode 100644 index 00000000000..489a4fd58ed Binary files /dev/null and b/docs/images/Guestalexdavid1.png differ diff --git a/docs/images/Guestalexdavid2.jpg b/docs/images/Guestalexdavid2.jpg new file mode 100644 index 00000000000..bed46515a08 Binary files /dev/null and b/docs/images/Guestalexdavid2.jpg differ diff --git a/docs/images/ListGuestsAfter.png b/docs/images/ListGuestsAfter.png new file mode 100644 index 00000000000..22c1cae2288 Binary files /dev/null and b/docs/images/ListGuestsAfter.png differ diff --git a/docs/images/ListOccupied.png b/docs/images/ListOccupied.png new file mode 100644 index 00000000000..be6f2c5c96c Binary files /dev/null and b/docs/images/ListOccupied.png differ diff --git a/docs/images/ListOccupiedResult.png b/docs/images/ListOccupiedResult.png new file mode 100644 index 00000000000..07592a816c1 Binary files /dev/null and b/docs/images/ListOccupiedResult.png differ diff --git a/docs/images/ListRecords.png b/docs/images/ListRecords.png new file mode 100644 index 00000000000..20fd106d11b Binary files /dev/null and b/docs/images/ListRecords.png differ diff --git a/docs/images/ListRooms.png b/docs/images/ListRooms.png new file mode 100644 index 00000000000..4a07a7a5a33 Binary files /dev/null and b/docs/images/ListRooms.png differ diff --git a/docs/images/ListRoomsByVacancySequenceDiagram.png b/docs/images/ListRoomsByVacancySequenceDiagram.png new file mode 100644 index 00000000000..3de2b43bcfc Binary files /dev/null and b/docs/images/ListRoomsByVacancySequenceDiagram.png differ diff --git a/docs/images/ListRoomsOccupied.png b/docs/images/ListRoomsOccupied.png new file mode 100644 index 00000000000..eb8b08676be Binary files /dev/null and b/docs/images/ListRoomsOccupied.png differ diff --git a/docs/images/ListRoomsVacant.png b/docs/images/ListRoomsVacant.png new file mode 100644 index 00000000000..e6f873506dd Binary files /dev/null and b/docs/images/ListRoomsVacant.png differ diff --git a/docs/images/ListVacant.png b/docs/images/ListVacant.png new file mode 100644 index 00000000000..6a8b5fb1a37 Binary files /dev/null and b/docs/images/ListVacant.png differ diff --git a/docs/images/ListVacantResult.png b/docs/images/ListVacantResult.png new file mode 100644 index 00000000000..3a9851e3dec Binary files /dev/null and b/docs/images/ListVacantResult.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 39d7aec4b33..8ebc9654f18 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/NavGuideCommandBoxImage.png b/docs/images/NavGuideCommandBoxImage.png new file mode 100644 index 00000000000..75612978181 Binary files /dev/null and b/docs/images/NavGuideCommandBoxImage.png differ diff --git a/docs/images/RecordCommandSequenceDiagram.png b/docs/images/RecordCommandSequenceDiagram.png new file mode 100644 index 00000000000..5b3d79bdeda Binary files /dev/null and b/docs/images/RecordCommandSequenceDiagram.png differ diff --git a/docs/images/Room001002.png b/docs/images/Room001002.png new file mode 100644 index 00000000000..4269704c57c Binary files /dev/null and b/docs/images/Room001002.png differ diff --git a/docs/images/SearchGuest.png b/docs/images/SearchGuest.png new file mode 100644 index 00000000000..863f678394a Binary files /dev/null and b/docs/images/SearchGuest.png differ diff --git a/docs/images/SearchGuestResult.png b/docs/images/SearchGuestResult.png new file mode 100644 index 00000000000..dbe1039e064 Binary files /dev/null and b/docs/images/SearchGuestResult.png differ diff --git a/docs/images/SearchRoom.png b/docs/images/SearchRoom.png new file mode 100644 index 00000000000..a24dd27a0d7 Binary files /dev/null and b/docs/images/SearchRoom.png differ diff --git a/docs/images/SearchRoomResult.png b/docs/images/SearchRoomResult.png new file mode 100644 index 00000000000..bcd7d64e4bf Binary files /dev/null and b/docs/images/SearchRoomResult.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 82c66f8f16e..14605e969ae 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 91488fd1a0f..a54fb741bae 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 4bb8b2ce591..9e6242294bd 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/bananatechs.png b/docs/images/bananatechs.png new file mode 100644 index 00000000000..6cbe834b1a7 Binary files /dev/null and b/docs/images/bananatechs.png differ diff --git a/docs/images/darrenhoon.png b/docs/images/darrenhoon.png new file mode 100644 index 00000000000..9402591a96c Binary files /dev/null and b/docs/images/darrenhoon.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..44d61dd9299 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/jianh0ng.png b/docs/images/jianh0ng.png new file mode 100644 index 00000000000..fdb16114728 Binary files /dev/null and b/docs/images/jianh0ng.png differ diff --git a/docs/images/peilinye.png b/docs/images/peilinye.png new file mode 100644 index 00000000000..88a02fb581f Binary files /dev/null and b/docs/images/peilinye.png differ diff --git a/docs/images/wilburrito.png b/docs/images/wilburrito.png new file mode 100644 index 00000000000..f9eef06eb9e Binary files /dev/null and b/docs/images/wilburrito.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..4f0f7ec6aa3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,17 @@ --- layout: page -title: AddressBook Level-3 +title: Trace2Gather --- [![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![codecov](https://codecov.io/gh/AY2122S1-CS2103T-T13-3/tp/branch/master/graph/badge.svg?token=BA7MNU8LJR)](https://codecov.io/gh/AY2122S1-CS2103T-T13-3/tp) ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +**Trace2Gather is a desktop application for hotel staff to manage their guests for contact tracing purposes.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using Trace2Gather, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing Trace2Gather, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/bananatechs.md b/docs/team/bananatechs.md new file mode 100644 index 00000000000..bc8634385a7 --- /dev/null +++ b/docs/team/bananatechs.md @@ -0,0 +1,52 @@ +--- +layout: page +title: Thomas's Project Portfolio Page +--- + +### Project: AddressBook Level 3 + +Trace2Gather is a desktop address book application used for managing hotel guests and rooms. 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. + +Given below are my contributions to the project. + +## Summary of Contributions + +### Code Contributed +[RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=T13-3&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=zoom&zA=peilinye&zR=AY2122S1-CS2103T-T13-3%2Ftp%5Bmaster%5D&zACS=150.4848484848485&zS=2021-09-17&zFS=T13-3&zU=2021-10-08&zMG=false&zFTF=commit&zFGS=groupByRepos&zFR=false) + +### Enhancements Implemented +* `checkin` and `checkout`: Added the ability to check guests in and out of rooms. + * **What it does**: allows the user to assign guests to a room and keep track of which rooms are occupied, as well as check them out when they leave the hotel. + * **Justification**: This feature is indispensable for managing hotels. + * **Highlights**: Conceived, designed and built the entire Residency system as a solution to problems posed by this feature. This system is a foundation for many new features to be built upon. See below for details. + * **Credits**: Original AB3 team, Darren for the Room system and many misc. fixes and tests. +

+* **Residency System (Design and Implementation)** + * The Residency system is responsible for storing the details of the stay of guests in a room. It is easy to work with, simple to expand upon, and relatively compatible with existing architecture. + * I designed the system to solve many problems otherwise inherent in the architecture. It decouples and serves as a mediator between the Room and Person systems, and is fast, reliable and easy to store. + * The system also serves as the foundation for many other features. For example: + * `Residency` provides a previously unavailable place to store additional details about stays, including the dates and times of check-ins / check-outs. + * `ResidencyBook` centralises the registration, lookup and removal of `Residency`s, and thus simplifies the storage of past stays separate from current stays, by simply having another `ResidencyBook` for them. +

+* Rewrote `Vacancy` class, greatly simplifying it and vanquishing bugs +

+* Misc. functionality + +### UG Contributions +* Re-writes to simplify language: + * Introduction + * Navigation Guide + * Section introductions +* Documentation for `checkin` and `checkout` +* Proof-reading, grammar improvements + +### DG Contributions +* Implementation section: Encapsulation of Hotel Stays (The Residency System) + +### Testing + * `CheckInCommand`, `CheckOutCommand` + * `CheckInCommandParser`, `CheckOutCommandParser` + * `ResidencyBook` + * `JsonAdaptedResidency` + * Added `TypicalAddressBook` + * Misc. other tests, fixes and functionality diff --git a/docs/team/darrenhoon.md b/docs/team/darrenhoon.md new file mode 100644 index 00000000000..80e059b4f1e --- /dev/null +++ b/docs/team/darrenhoon.md @@ -0,0 +1,63 @@ +--- +layout: page +title: Darren Hoon's Project Portfolio Page +--- + +## Project: Trace2Gather +Trace2Gather is a desktop address book application used for managing hotel guests and rooms. 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. + +Given below are my contributions to the project. + +## Summary of Contributions + +### Code Contributed +[RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=t13-3&sort=groupTitle&sortWithin=title&since=2021-09-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=darrenhoon&tabRepo=AY2122S1-CS2103T-T13-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +### Enhancements Implemented +1. Linked Database's Room details to GUI, where Rooms are displayed on the GUI's list of rooms. + * What it does: Allows users to visually see the rooms that were saved in the database and their last updated status. + * Justification: This feature improves the product significantly because being able to visually check a room's status, such as its Occupancy Status, helps it's users to identify a room's status quickly and increases response time to the user requesting such information. + * Highlights: This enhancement affects user interaction with the application. It required an in-depth analysis and understanding of the GUI codebase and jackSon's syntax, such as @FXML tags having only small characters + * Credit: jackSon documentation, stackOverflow +2. Find Room command to enable users to search for specific rooms + * What it does: Allows users to search for specific rooms in the given list of rooms + * Justification: This feature improves the product significantly as being able to quickly search for specific rooms helps the user to retrieve information about the room faster as compared to manually scrolling down till they find the room + * Highlights: This enhancement required us to put ourselves in the shoes of a potential user and what were the pain points that might arise from our application without this feature. + * Credit: Existing UML Diagrams, Well-thought out code structure that enables easy extensibility. +3. Added GUI elements for Room and History card panels in the application. + * What it does: Allow users to see in detail the changes to the state of the application based on their actions and their accompanying information. + * Justification: Being able to visually see which guests are in which rooms enable faster retrieval of information. Colour coding for vacancy and guests enables faster information retrieval for the user by allowing users to gain an intuitive understanding of the status of the room card's layout and distinction from one room to the next. +
Displaying the residencies on the frontend via a list affords the user with the choice to interact with the existing database either via the GUI application or via direct JSON accesses. Being able to access it from the GUI along with the commands we added to filter residencies help users to identify residencies of interest at a much faster rate. + * Highlights: These enhancements enabled us to visually check if our system is registering the changes based on the commands we type and the colour coding enables a clear distinction and focus of each room and its internal details from another. + * Credit: Original AB3 team for making the code structure highly extensible and their FlowPane's styling in the CSS, [coolors](https://coolors.co/) to help with the colour coordination. + + +### UG Contributions +* Introduction in each section +* Purpose of Trace2Gather and Purpose of Guide +* Navigation Guide +* Redirected Help Window's link to our project's User Guide Page + +### DG Contributions +* Implemented [Use Cases Page](https://ay2122s1-cs2103t-t13-3.github.io/tp/UseCases.html) +* Amended UML Diagrams +* Added "Appendix: Effort" section + +### Team-Based Tasks Contributions +1. Main bug finder and user tester for application, reporting bugs to the team to be fixed. +2. Maintaining issues tracker. +3. Designing and making our product icon. +4. Releasing v1.4 (trial) of product. +5. Testing some new features that were not covered by others. +6. Providing boilerplate code for team to start work in first week of team project. + +### Review / Mentoring Contributions +1. Coordinated with the team frequently and kept track of each other's progress. +2. Assisted in solving problems raised by the team when developing the product. +3. Reviewed Pull Requests and assisted in adding test cases to ensure that code coverage is not significantly reduced. +4. Assisted in debugging issues raised by the team or others. + +### Contributions Beyond the Project Team +* PRs reviewed (with non-trivial review comments): [\#144](https://github.com/nus-cs2103-AY2122S1/ip/pull/144), [\#486](https://github.com/nus-cs2103-AY2122S1/ip/pull/486), [\#173](https://github.com/nus-cs2103-AY2122S1/ip/pull/173) +* Contributed to forum discussions: [\#176](https://github.com/nus-cs2103-AY2122S1/forum/issues/176), [\#183](https://github.com/nus-cs2103-AY2122S1/forum/issues/183), [\#296](https://github.com/nus-cs2103-AY2122S1/forum/issues/296) +* Updated module setup guide for all students to reflect latest intelliJ settings: [\#3](https://github.com/se-edu/guides/pull/3) diff --git a/docs/team/jianh0ng.md b/docs/team/jianh0ng.md new file mode 100644 index 00000000000..b5eb91f0f7e --- /dev/null +++ b/docs/team/jianh0ng.md @@ -0,0 +1,32 @@ +--- +layout: page +title: Jian Hong's Project Portfolio Page +--- + +### Project: Trace2Gather + +Trace2Gather is a desktop address book application used for managing hotel guests and rooms to help in contact tracing purposes. 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. + +Given below are my contributions to the project. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=jianh0ng&sort=groupTitle&sortWithin=title&since=2021-09-17&timeframe=commit&mergegroup=&groupSelect=groupByAuthors&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=jianh0ng&tabRepo=AY2122S1-CS2103T-T13-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)

+* **Enhancements implemented**: Added the records section of the addressbook to keep track of the history of residencies as well as the search functionality for the records. + * What it does: Allows the user to view past residencies for contact tracing and search for relevant residencies by person, room number etc... + * Justification: This feature improves the product significantly because it helps our users retrieve information quickly for contact tracing purposes. + * Highlights: The search commands allows for users to enter multiple keywords and only displays results that match all the keywords. The design of this search differs from the one for searching guests, because we want to enable users to do more precise searches for records to minimise delays in contact tracing. The implementation also required changes to other commands to ensure that the data displayed is updated accordingly.

+* **Contributions to the UG**: + * Added documentation for listing rooms as well as records + * Details for the search method for records + * Keep several images in the UG updated with changes to the UI. + * Updated several other commands in accordance to feedback from peers.

+* **Contributions to the DG**: + * Updated several UML diagrams as well + * Implementation details for the past records feature + * Updated parts of Instructions for Manual Testing + +
+ +* **Contributions to team-based tasks**: + * Managed releases v1.2 to v1.3 (final) + * Managed several weekly team tasks such as product demo for v1.2 and v1.3 alongside teammates. + * Added target user profile, value proposition and use cases in the developer guide diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index 773a07794e2..00000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: page -title: John Doe's Project Portfolio Page ---- - -### Project: AddressBook Level 3 - -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -Given below are my contributions to the project. - -* **New Feature**: 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}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() - -* **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub - -* **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) - -* **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/peilinye.md b/docs/team/peilinye.md new file mode 100644 index 00000000000..0757722cab6 --- /dev/null +++ b/docs/team/peilinye.md @@ -0,0 +1,61 @@ +--- +layout: page +title: Ye Pei Lin's Project Portfolio Page +--- + +# Project: Trace2Gather + +Trace2Gather is a desktop address book application used for managing hotel guests and rooms to aid in contact tracing efforts. 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. + +Given below are my contributions to the project. + +* **Code contributed:** +[RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&tabAuthor=peilinye&tabRepo=AY2122S1-CS2103T-T13-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + + +* **New Features:** + * Added the functionality of listing rooms by vacancy status. + * What it does: Shows users the rooms that are either vacant or occupied in the GUI. + * Justification: This feature improves the product significantly by making it more convenient for users to view rooms of a certain vacancy status to easily check in guests into a vacant room or checkout an occupied room. + * Credits: This feature was built on the existing `list rooms` feature. + * Added the functionality of adding rooms with tags. + * What it does: Allows the user to add a specified number of rooms with one or more tags. + * Justification: This feature improves the product significantly by providing a way for users to add more rooms with additional information of the rooms. + * Highlights: This feature affected the current implementation of the `room` object as there was an additional attribute of tags. The implementation for storing and loading information from JSON files had to be updated. Support for parsing the new command had to be added as well. + +* **Enhancements to existing features:** + * Added UI components necessary for displaying the list of rooms, such as `RoomCard` and `RoomListPanel`. + * Added Tags as an attribute of rooms to represent different types of rooms or any optional additional information of the rooms. + * Added validity checking of input for the `room` command to search for specific rooms, so an error message will be shown when user input is invalid. + +* **Documentation:** + * User Guide: + * Added documentation for the features `list rooms vacant` and `list rooms occupied`. + * Added documentation for the `addroom` feature. + * Ensure overall formatting of user guide is shown correctly. [#133](https://github.com/AY2122S1-CS2103T-T13-3/tp/pull/133) + * Added the screenshots for the following commands: `guest`, `checkin`, `checkout`, `room`. + * Updated the command summary table. + * Developer Guide: + * Added use cases, user stories. + * Added the part on listing rooms by vacancy feature under the implementation section, including the sequence diagram. [#189](https://github.com/AY2122S1-CS2103T-T13-3/tp/pull/189/files) + * Added manual testing instructions for dealing with missing or corrupted data files. [#206](https://github.com/AY2122S1-CS2103T-T13-3/tp/pull/206/files) + * Added acknowledgements to the AB3 project. + +* **Testing:** + * Logic: + * `AddressBookParserTest`: `AddRoomCommand` + * `AddRoomCommandParserTest`, `AddRoomCommandTest` + * `FindRoomCommandParserTest` + * `ListCommandParserTest`, `ListCommandTest` + * Model: + * `RoomBuilder` + +* **Team-Based tasks:** + * Updated User Guide based on peer feedback. + * Ensured overall formatting of User Guide and Developer Guide was correct. + * Added user stories to Developer Guide. + * Maintained issue tracker alongside teammates. + +* **Community:** + * PRs reviewed (with non-trivial review comments): [#215](https://github.com/nus-cs2103-AY2122S1/ip/pull/215), [#32](https://github.com/nus-cs2103-AY2122S1/ip/pull/183) + * Reported bugs of another team during PE-D diff --git a/docs/team/wilburrito.md b/docs/team/wilburrito.md new file mode 100644 index 00000000000..a587954370b --- /dev/null +++ b/docs/team/wilburrito.md @@ -0,0 +1,39 @@ +--- +layout: page +title: Wilbur's Project Portfolio Page +--- + +### Project: Trace2Gather + +Trace2Gather is a desktop address book application used for managing hotel guests and rooms. 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. + +Given below are my contributions to the project. + +* **New Feature**: Search for guests by name + * What it does: Gives the user the ability to search for guests by name. Mirrors the logic from AB3 to search for Persons, with a different keyword. + * Justification: This feature would allow the user check if a certain guest has indeed stayed at the hotel in question. + * Highlights: Was not very challenging as the logic was quite similar to AB3's `find` logic. + * Credit: Original AB3 team. +* **Code contributed**: + [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=wilburrito&sort=groupTitle&sortWithin=title&since=2021-09-17&timeframe=commit&mergegroup=&groupSelect=groupByAuthors&breakdown=false&tabOpen=true&tabAuthor=wilburrito&tabRepo=AY2122S1-CS2103T-T13-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&tabType=authorship) + +* **Enhancements to existing features**: + * Changed the identification logic for Person objects. + * Persons were initially identified by an `Id` field, which was regenerated every time Trace2Gather is opened. + * Changed the `Id` field to an `Nric` field instead, which guarantees uniqueness and may eliminate bugs. + * Edited the storage such that the `Nric` of a person is stored in. + * Edited logic for prevention of duplicate person objects + * Changed the way duplications are handled. Instead of being handled by comparing the names of Person objects, Trace2Gather now does it by using its Nric field. + * This allows for Person objects to have the same name, which is more realistic as compared to disallowing duplicate names. + +* **Documentation**: + * User Guide: + * Adapted user guide to suit our project. + * Fixed example usage of code, changed the tone of language used to be more engaging. + * Fixed most of the UG bugs from the PE-D. + * Developer Guide: + * Adapted UML diagrams to suit our project. + * Adapted developer guide to suit our project. + * Fixed any DG bugs that may have come up from changing the implementation of the AB3 code to suit Trace2Gather. + * Miscellaneous + * Adapted documentation to remove traces of AB3. diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/address/Main.java index 052a5068631..206382c4bde 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/seedu/address/Main.java @@ -8,7 +8,7 @@ * This is a workaround for the following error when MainApp is made the * entry point of the application: * - * Error: JavaFX runtime components are missing, and are required to run this application + * Error: JavaFX runtime components are missing, and are required to run this application. * * The reason is that MainApp extends Application. In that case, the * LauncherHelper will check for the javafx.graphics module to be present diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..32977ef7c65 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -36,7 +36,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 0, true); + public static final Version VERSION = new Version(0, 6, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -62,6 +62,7 @@ public void init() throws Exception { initLogging(config); model = initModelManager(storage, userPrefs); + ModelManager.setInstance(model); logic = new LogicManager(model, storage); @@ -88,6 +89,9 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { } catch (IOException e) { logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); initialData = new AddressBook(); + } catch (NullPointerException e) { + logger.warning("Invalid information in the file. Will be starting with and empty AddressBook"); + initialData = new AddressBook(); } return new ModelManager(initialData, userPrefs); diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java index ba33653be67..e4e0cebefb5 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/address/commons/core/GuiSettings.java @@ -11,7 +11,7 @@ public class GuiSettings implements Serializable { private static final double DEFAULT_HEIGHT = 600; - private static final double DEFAULT_WIDTH = 740; + private static final double DEFAULT_WIDTH = 1500; private final double windowWidth; private final double windowHeight; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..0abc59982cd 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -7,7 +7,11 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = + "The person index provided is invalid. Index should be one that is displayed in the Guests panel below."; + public static final String MESSAGE_INVALID_ROOM_DISPLAYED_INDEX = + "The room index provided is invalid. Index should be one that is displayed in the Rooms panel below."; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - + public static final String MESSAGE_ROOM_LISTED_OVERVIEW = "Rooms filtered!"; + public static final String MESSAGE_RECORDS_LISTED_OVERVIEW = "Records filtered!"; } diff --git a/src/main/java/seedu/address/commons/core/listroomargs/ListRoomArg.java b/src/main/java/seedu/address/commons/core/listroomargs/ListRoomArg.java new file mode 100644 index 00000000000..432f4247f8c --- /dev/null +++ b/src/main/java/seedu/address/commons/core/listroomargs/ListRoomArg.java @@ -0,0 +1,54 @@ +package seedu.address.commons.core.listroomargs; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +public class ListRoomArg { + public static final String MESSAGE_CONSTRAINTS = "List room extra arguments should be 'vacant' or 'occupied'."; + private static final String VACANT = "vacant"; + private static final String OCCUPIED = "occupied"; + + private String arg; + + /** + * Constructs a ListRoomArgType object with the given argument. + * + * @param arg + */ + public ListRoomArg(String arg) { + requireNonNull(arg); + checkArgument(isValidListType(arg), MESSAGE_CONSTRAINTS); + this.arg = arg; + } + + /** + * Returns true if a given string is a valid additional argument to list room. + */ + public static boolean isValidListType(String type) { + return type.equals(VACANT) || type.equals(OCCUPIED); + } + + /** + * Returns true if the ListRoomArg is "vacant". + */ + public boolean isVacant() { + return this.arg.equals(VACANT); + } + + /** + * Returns true if the ListRoomArg is "occupied". + */ + public boolean isOccupied() { + return this.arg.equals(OCCUPIED); + } + + /** + * Returns true if both refer to ListRoomArg with the same argument. + */ + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ListRoomArg // instanceof handles nulls + && arg.equals(((ListRoomArg) other).arg)); // state check + } +} diff --git a/src/main/java/seedu/address/commons/core/listtype/ListType.java b/src/main/java/seedu/address/commons/core/listtype/ListType.java new file mode 100644 index 00000000000..69296e33957 --- /dev/null +++ b/src/main/java/seedu/address/commons/core/listtype/ListType.java @@ -0,0 +1,62 @@ +package seedu.address.commons.core.listtype; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +public class ListType { + public static final String MESSAGE_CONSTRAINTS = "List types should be 'rooms', 'guests' or 'records'."; + private static final String ROOMS = "rooms"; + private static final String GUESTS = "guests"; + private static final String RECORDS = "records"; + + private String type; + + /** + * Constructs a ListType object with given type. + * + * @param type + */ + public ListType(String type) { + requireNonNull(type); + checkArgument(isValidListType(type), MESSAGE_CONSTRAINTS); + this.type = type; + } + + /** + * Returns true if a given string is a valid list type. + */ + public static boolean isValidListType(String type) { + return type.equals(ROOMS) || type.equals(GUESTS) || type.equals(RECORDS); + } + + /** + * Returns true if the ListType is a GUESTS type. + */ + public boolean isGuestsType() { + return this.type.equals(GUESTS); + } + + /** + * Returns true if the ListType is a ROOMS type. + */ + public boolean isRoomsType() { + return this.type.equals(ROOMS); + } + + /** + * Returns true if the ListType is a RECORDS type. + */ + public boolean isRecordsType() { + return this.type.equals(RECORDS); + } + + /** + * Returns true if both refer to ListType with the same argument. + */ + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ListType // instanceof handles nulls + && type.equals(((ListType) other).type)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..4f89bf8cb57 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -9,6 +9,8 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.residency.Residency; +import seedu.address.model.room.Room; /** * API of the Logic component @@ -33,6 +35,12 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of rooms */ + ObservableList getFilteredRoomList(); + + /** Returns an unmodifiable view of the filtered list of records */ + ObservableList getFilteredRecordList(); + /** * Returns the user prefs' address book file path. */ @@ -47,4 +55,5 @@ public interface Logic { * Set the user prefs' GUI settings. */ void setGuiSettings(GuiSettings guiSettings); + } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..c81f66a63be 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -15,6 +15,8 @@ import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.residency.Residency; +import seedu.address.model.room.Room; import seedu.address.storage.Storage; /** @@ -64,6 +66,16 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getFilteredRoomList() { + return model.getFilteredRoomList(); + } + + @Override + public ObservableList getFilteredRecordList() { + return model.getFilteredRecordList(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 71656d7c5c8..762c1a1ff9e 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -4,6 +4,7 @@ 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_NRIC; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; @@ -18,23 +19,24 @@ 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 address book. " + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_NRIC + "ID " + "[" + 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_NRIC + "S9876543A" + + PREFIX_TAG + "SeafoodAllergy"; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_SUCCESS = "New guest added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This guest already exists in the address book"; private final Person toAdd; diff --git a/src/main/java/seedu/address/logic/commands/AddRoomCommand.java b/src/main/java/seedu/address/logic/commands/AddRoomCommand.java new file mode 100644 index 00000000000..d43c91511ea --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddRoomCommand.java @@ -0,0 +1,77 @@ +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_TAG; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ROOMS; + +import java.util.HashSet; +import java.util.Set; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.tag.Tag; + +public class AddRoomCommand extends Command { + + public static final String COMMAND_WORD = "addroom"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds the specified number of rooms with the specified type. " + + "Maximum number of rooms allowed is 999.\n" + + "Parameters: NUMBER_OF_ROOMS (must be a positive integer) " + + PREFIX_TAG + "TAG\n" + + "Example: " + COMMAND_WORD + " 50 " + + PREFIX_TAG + "normal"; + + public static final String MESSAGE_SUCCESS = "%1$s new rooms of type %2$s added."; + public static final String MESSAGE_EXCEEDED_MAX_NUMBER_OF_ROOMS = + "Adding %1$s more room(s) would exceed the maximum 999 rooms allowed."; + public static final String MESSAGE_INVALID_INTEGER = "Invalid number of rooms entered."; + + private final int number; + private final Set tags = new HashSet<>(); + + /** + * Creates an AddRoomCommand to add the specified number of {@code Rooms} with the specified {@code Tags} + * + * @param number The number of rooms to be added. + * @param tags The tags to be set for the rooms. + */ + public AddRoomCommand(int number, Set tags) { + requireAllNonNull(number, tags); + this.number = number; + this.tags.addAll(tags); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + model.updateFilteredRoomList(PREDICATE_SHOW_ALL_ROOMS); + int numRooms = model.getNumberOfRooms(); + if (this.number + numRooms > 999) { + throw new CommandException(String.format(MESSAGE_EXCEEDED_MAX_NUMBER_OF_ROOMS, number)); + } + + int totalNumOfRooms = numRooms + number; + for (int i = numRooms + 1; i <= totalNumOfRooms; i++) { + String roomNumber = String.format("%03d", i); + Room room = new Room(new RoomNumber(roomNumber), tags); + model.addRoom(room); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, number, tags.toString())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddRoomCommand // instanceof handles nulls + && number == ((AddRoomCommand) other).number + && tags.equals(((AddRoomCommand) other).tags)); + } + +} 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 00000000000..9c745c00e06 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CheckInCommand.java @@ -0,0 +1,114 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.room.Room; +import seedu.address.model.room.Vacancy; + +/** + * Checks a group of persons into a room + */ +public class CheckInCommand extends Command { + public static final String COMMAND_WORD = "checkin"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Checks in guests to a room " + + "using their index numbers used in the displayed guest list.\n" + + "Parameters: INDEX_ROOM (must be a positive integer) " + + "g/ [GUEST_INDEX] (can be used multiple times)\n" + + "Example: " + COMMAND_WORD + " 1 " + + "g/ 43 g/ 22"; + + public static final String MESSAGE_CHECKIN_SUCCESS = "Room Checked In: %1$s"; + public static final String MESSAGE_NO_GUESTS = "At least one person must be checked into the room."; + public static final String MESSAGE_ROOM_IS_OCCUPIED = "Room is currently occupied."; + public static final String MESSAGE_PERSON_ALREADY_CHECKED_IN = + "One or more guests have already been checked into another room."; + public static final String MESSAGE_PERSON_INVALID_INDEX = "Invalid guest number for one or more guests."; + public static final String MESSAGE_ROOM_INVALID_INDEX = "Invalid room number."; + + private final Index roomIndex; + private final List guestIndexes; + + /** + * @param roomIndex The index of the room to be checked into + * @param guestIndexes A list of the indexes of guests to check into the room + */ + public CheckInCommand(Index roomIndex, List guestIndexes) { + this.roomIndex = roomIndex; + this.guestIndexes = guestIndexes; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownPersonList = model.getFilteredPersonList(); + List lastShownRoomList = model.getFilteredRoomList(); + + if (roomIndex.getZeroBased() >= lastShownRoomList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ROOM_DISPLAYED_INDEX); + } + for (Index guestIndex : guestIndexes) { + if (guestIndex.getZeroBased() >= lastShownPersonList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + } + + Room roomToEdit = lastShownRoomList.get(roomIndex.getZeroBased()); + if (model.getResidency(roomToEdit).isPresent()) { + throw new CommandException(MESSAGE_ROOM_IS_OCCUPIED); + } + + Set guests = new HashSet<>(); + for (Index guestIndex : guestIndexes) { + Person guestToAdd = lastShownPersonList.get(guestIndex.getZeroBased()); + + if (model.getResidency(guestToAdd).isPresent()) { + throw new CommandException( + String.format(MESSAGE_PERSON_ALREADY_CHECKED_IN, guestIndex.getOneBased())); + } + + guests.add(guestToAdd); + } + if (guests.isEmpty()) { + throw new CommandException(MESSAGE_NO_GUESTS); + } + + Room editedRoom = new Room(roomToEdit.getRoomNumber(), Vacancy.OCCUPIED, roomToEdit.getTags()); + + model.setRoom(roomToEdit, editedRoom); + model.register(editedRoom, guests); + model.updateFilteredRoomList(Model.PREDICATE_SHOW_ALL_ROOMS); + model.updateFilteredRecordList(Model.PREDICATE_SHOW_ALL_RECORDS); + return new CommandResult(String.format(MESSAGE_CHECKIN_SUCCESS, editedRoom)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CheckInCommand)) { + return false; + } + + // state check + CheckInCommand c = (CheckInCommand) other; + + return roomIndex.equals(c.roomIndex) + && guestIndexes.equals(c.guestIndexes); + } + +} 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 00000000000..ce2c23f140b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CheckOutCommand.java @@ -0,0 +1,70 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.residency.Residency; +import seedu.address.model.room.Room; + +/** + * Checks out all the persons in a room + */ +public class CheckOutCommand extends Command { + public static final String COMMAND_WORD = "checkout"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Checks out all the guests in a room " + + "using it's index number in the displayed list (not room number).\n" + + "Parameters: INDEX_ROOM (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_CHECKOUT_SUCCESS = "Room Checked Out: %1$s"; + public static final String MESSAGE_ROOM_IS_VACANT = "Room is already vacant."; + + private final Index roomIndex; + + /** + * Creates a CheckOutCommand to check out the {@code Room} at the specified {@code Index}. + */ + public CheckOutCommand(Index roomIndex) { + this.roomIndex = roomIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownRoomList = model.getFilteredRoomList(); + + if (roomIndex.getZeroBased() >= lastShownRoomList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ROOM_DISPLAYED_INDEX); + } + + Room roomToEdit = lastShownRoomList.get(roomIndex.getZeroBased()); + Optional residency = model.getResidency(roomToEdit); + if (residency.isEmpty()) { + throw new CommandException(MESSAGE_ROOM_IS_VACANT); + } + + //Resets room to default (vacant, no guests) + Room editedRoom = new Room(roomToEdit.getRoomNumber(), roomToEdit.getTags()); + model.setRoom(roomToEdit, editedRoom); + residency.ifPresent(model::record); + residency.ifPresent(model::removeResidency); + residency.ifPresent(Residency::checkOut); + model.updateFilteredRoomList(Model.PREDICATE_SHOW_ALL_ROOMS); + return new CommandResult(String.format(MESSAGE_CHECKOUT_SUCCESS, editedRoom)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CheckOutCommand // instanceof handles nulls + && roomIndex.equals(((CheckOutCommand) other).roomIndex)); // 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 9c86b1fa6e4..58d7399d8ef 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -11,7 +11,7 @@ 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 = "Trace2Gather has been cleared!"; @Override diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 02fd256acba..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 7e36114902f..109f85c492a 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -4,6 +4,7 @@ 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_NRIC; 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; @@ -22,6 +23,7 @@ import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; @@ -39,6 +41,7 @@ public class EditCommand extends Command { + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_NRIC + "ID] " + "[" + PREFIX_EMAIL + "EMAIL] " + "[" + PREFIX_ADDRESS + "ADDRESS] " + "[" + PREFIX_TAG + "TAG]...\n" @@ -46,9 +49,10 @@ public class EditCommand extends Command { + 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_EDIT_PERSON_SUCCESS = "Edited Guest: %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."; + public static final String MESSAGE_INVALID_INDEX = "Index should be a positive integer."; private final Index index; private final EditPersonDescriptor editPersonDescriptor; @@ -83,6 +87,9 @@ public CommandResult execute(Model model) throws CommandException { model.setPerson(personToEdit, editedPerson); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); } @@ -99,7 +106,14 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + Nric updatedNric = editPersonDescriptor.getNric().orElse(personToEdit.getNric()); + + return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedNric, updatedTags); + } + + @Override + public String toString() { + return this.editPersonDescriptor.toString(); } @Override @@ -129,6 +143,7 @@ public static class EditPersonDescriptor { private Phone phone; private Email email; private Address address; + private Nric nric; private Set tags; public EditPersonDescriptor() {} @@ -141,6 +156,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); + setNric(toCopy.nric); setAddress(toCopy.address); setTags(toCopy.tags); } @@ -149,7 +165,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, phone, email, nric, address, tags); } public void setName(Name name) { @@ -176,6 +192,14 @@ public Optional getEmail() { return Optional.ofNullable(email); } + public void setNric(Nric nric) { + this.nric = nric; + } + + public Optional getNric() { + return Optional.ofNullable(nric); + } + public void setAddress(Address address) { this.address = address; } @@ -201,6 +225,12 @@ public Optional> getTags() { return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); } + @Override + public String toString() { + return this.name.toString() + this.phone.toString() + this.email.toString() + this.email.toString() + + this.nric.toString() + this.address.toString() + this.tags.toString(); + } + @Override public boolean equals(Object other) { // short circuit if same object @@ -219,6 +249,7 @@ public boolean equals(Object other) { return getName().equals(e.getName()) && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) + && getNric().equals(e.getNric()) && getAddress().equals(e.getAddress()) && getTags().equals(e.getTags()); } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindGuestCommand.java similarity index 69% rename from src/main/java/seedu/address/logic/commands/FindCommand.java rename to src/main/java/seedu/address/logic/commands/FindGuestCommand.java index d6b19b0a0de..d018f03bae6 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindGuestCommand.java @@ -7,21 +7,20 @@ import seedu.address.model.person.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all Guests in Trace2Gather whose name contains any of the argument keywords. * Keyword matching is case insensitive. */ -public class FindCommand extends Command { +public class FindGuestCommand extends Command { + public static final String COMMAND_WORD = "guest"; - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all guests whose names contain any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + "Example: " + COMMAND_WORD + " alice bob charlie"; private final NameContainsKeywordsPredicate predicate; - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindGuestCommand(NameContainsKeywordsPredicate predicate) { this.predicate = predicate; } @@ -36,7 +35,7 @@ public CommandResult execute(Model model) { @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 + || (other instanceof FindGuestCommand // instanceof handles nulls + && predicate.equals(((FindGuestCommand) other).predicate)); // state check } } diff --git a/src/main/java/seedu/address/logic/commands/FindRecordCommand.java b/src/main/java/seedu/address/logic/commands/FindRecordCommand.java new file mode 100644 index 00000000000..b6342858b5d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindRecordCommand.java @@ -0,0 +1,40 @@ +package seedu.address.logic.commands; + + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.residency.exceptions.ResidencyContainsKeywordsPredicate; + +/** + * Finds and lists all past residencies in Trace2Gather whose data contains any of the argument keywords. + * Keyword matching is case sensitive. + */ +public class FindRecordCommand extends Command { + public static final String COMMAND_WORD = "record"; + + //TODO add desc + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds the records with the given keywords."; + + private final ResidencyContainsKeywordsPredicate predicate; + + public FindRecordCommand(ResidencyContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredRecordList(predicate); + return new CommandResult(Messages.MESSAGE_RECORDS_LISTED_OVERVIEW); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindRecordCommand // instanceof handles nulls + && predicate.equals(((FindRecordCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindRoomCommand.java b/src/main/java/seedu/address/logic/commands/FindRoomCommand.java new file mode 100644 index 00000000000..1467fb736fd --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindRoomCommand.java @@ -0,0 +1,42 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.room.RoomNumberContainsKeywordsPredicate; + +/** + * Finds and lists all rooms in address book whose room number contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindRoomCommand extends Command { + public static final String COMMAND_WORD = "room"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds the specific room number(s) provided. " + + "Room numbers should be 3 digits.\n" + + "Example: " + COMMAND_WORD + " 001 002"; + public static final String MESSAGE_INVALID_ROOM_NUMBER = "One or more room numbers given are invalid." + + " Room numbers should be a 3 digit number."; + + private final RoomNumberContainsKeywordsPredicate predicate; + + public FindRoomCommand(RoomNumberContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredRoomList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_ROOM_LISTED_OVERVIEW)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindRoomCommand // instanceof handles nulls + && predicate.equals(((FindRoomCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..e24a1230ace 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,9 +1,18 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_RECORDS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ROOMS; +import java.util.Objects; +import java.util.function.Predicate; + +import seedu.address.commons.core.listtype.ListType; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.room.Room; /** * Lists all persons in the address book to the user. @@ -12,13 +21,98 @@ 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_GUESTS = "Listed all guests"; + + public static final String MESSAGE_SUCCESS_ROOMS = "Listed all rooms"; + + public static final String MESSAGE_SUCCESS_RECORDS = "Listed all records"; + + public static final String MESSAGE_SUCCESS_ROOMS_TYPE = "Listed all rooms of indicated type"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Lists guests, rooms, or records, based on given arguments.\n" + + "Parameters: LISTTYPE ('guests', 'rooms' or 'records'), " + + "(optional) LISTROOMARG ('vacant' or 'occupied') (only for listing rooms).\n" + + "Examples: " + COMMAND_WORD + " guests, " + + COMMAND_WORD + " records, " + + COMMAND_WORD + " rooms, " + + COMMAND_WORD + " rooms occupied, " + + COMMAND_WORD + " rooms vacant"; + private ListType listType; + + private Predicate predicate; + + public ListCommand(ListType listType) { + this.listType = listType; + } + + /** + * Constructs a ListCommand object with the ListType and predicate. + * + * @param listType The type to be listed. + * @param predicate The predicate to filter the objects to be listed by. + */ + public ListCommand(ListType listType, Predicate predicate) { + this.listType = listType; + this.predicate = predicate; + } + + /** + * Returns true if the ListCommand lists guests. + */ + public boolean isGuests() { + return this.listType.isGuestsType(); + } + + /** + * Returns true if the ListCommand lists rooms. + */ + public boolean isRooms() { + return this.listType.isRoomsType(); + } + + /** + * Returns true if the ListCommand lists records. + */ + public boolean isRecords() { + return this.listType.isRecordsType(); + } @Override - public CommandResult execute(Model model) { + public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); + if (this.isGuests()) { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(MESSAGE_SUCCESS_GUESTS); + } else if (this.isRooms()) { + if (this.predicate == null) { + model.updateFilteredRoomList(PREDICATE_SHOW_ALL_ROOMS); + return new CommandResult(MESSAGE_SUCCESS_ROOMS); + } else { + model.updateFilteredRoomList(predicate); + return new CommandResult(MESSAGE_SUCCESS_ROOMS_TYPE); + } + } else if (this.isRecords()) { + model.updateFilteredRecordList(PREDICATE_SHOW_ALL_RECORDS); + return new CommandResult(MESSAGE_SUCCESS_RECORDS); + } else { + throw new CommandException(MESSAGE_INVALID_COMMAND_FORMAT); + } + } + + /** + * Returns true if the both are ListCommands with the same arguments. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof ListCommand) { + ListCommand otherListCommand = (ListCommand) other; + return listType.equals(otherListCommand.listType) + && Objects.equals(predicate, otherListCommand.predicate); + } + return false; } } diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e8..0c89f86b911 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -4,6 +4,7 @@ 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_NRIC; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; @@ -15,6 +16,7 @@ import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; @@ -31,9 +33,10 @@ 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); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, + PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_NRIC, PREFIX_TAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_NRIC) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } @@ -42,9 +45,9 @@ public AddCommand parse(String args) throws ParseException { 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()); + Nric nric = ParserUtil.parseNric(argMultimap.getValue(PREFIX_NRIC).get()); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); + Person person = Person.createPerson(name, phone, email, address, nric, tagList); return new AddCommand(person); } diff --git a/src/main/java/seedu/address/logic/parser/AddRoomCommandParser.java b/src/main/java/seedu/address/logic/parser/AddRoomCommandParser.java new file mode 100644 index 00000000000..e8c9c6751bc --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddRoomCommandParser.java @@ -0,0 +1,54 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.AddRoomCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new AddRoomCommand object + */ +public class AddRoomCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddRoomCommand + * and returns an AddRoomCommand object for execution. + * @throws ParseException if the user input does not conform to the expected format + */ + public AddRoomCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_TAG) + || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddRoomCommand.MESSAGE_USAGE)); + } + + int numberOfRooms; + try { + numberOfRooms = ParserUtil.parseNumber(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(AddRoomCommand.MESSAGE_INVALID_INTEGER); + } + + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + return new AddRoomCommand(numberOfRooms, 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()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..1a3691acf4c 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -7,12 +7,16 @@ import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddRoomCommand; +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.FindGuestCommand; +import seedu.address.logic.commands.FindRecordCommand; +import seedu.address.logic.commands.FindRoomCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.parser.exceptions.ParseException; @@ -50,17 +54,20 @@ public Command parseCommand(String userInput) throws ParseException { case EditCommand.COMMAND_WORD: return new EditCommandParser().parse(arguments); - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - case ClearCommand.COMMAND_WORD: return new ClearCommand(); - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); + case FindGuestCommand.COMMAND_WORD: + return new FindGuestCommandParser().parse(arguments); + + case FindRoomCommand.COMMAND_WORD: + return new FindRoomCommandParser().parse(arguments); + + case FindRecordCommand.COMMAND_WORD: + return new FindRecordCommandParser().parse(arguments); case ListCommand.COMMAND_WORD: - return new ListCommand(); + return new ListCommandParser().parse(arguments); case ExitCommand.COMMAND_WORD: return new ExitCommand(); @@ -68,6 +75,15 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case CheckInCommand.COMMAND_WORD: + return new CheckInCommandParser().parse(arguments); + + case CheckOutCommand.COMMAND_WORD: + return new CheckOutCommandParser().parse(arguments); + + case AddRoomCommand.COMMAND_WORD: + return new AddRoomCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } 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 00000000000..05d1be49d91 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CheckInCommandParser.java @@ -0,0 +1,49 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GUEST; + +import java.util.ArrayList; +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.CheckInCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * 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 a CheckInCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public CheckInCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GUEST); + + Index roomIndex; + try { + roomIndex = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(CheckInCommand.MESSAGE_ROOM_INVALID_INDEX); + } + + List guestList = argMultimap.getAllValues(PREFIX_GUEST); + List guestIndexes = new ArrayList<>(); + try { + for (String guestString : guestList) { + guestIndexes.add(ParserUtil.parseIndex(guestString)); + } + } catch (IllegalValueException ive) { + throw new ParseException(CheckInCommand.MESSAGE_PERSON_INVALID_INDEX); + } + + return new CheckInCommand(roomIndex, guestIndexes); + } + +} 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 00000000000..f0ee0a6f84d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CheckOutCommandParser.java @@ -0,0 +1,32 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CheckOutCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * 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 a CheckOutCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public CheckOutCommand parse(String args) throws ParseException { + requireNonNull(args); + try { + Index roomIndex = ParserUtil.parseIndex(args); + return new CheckOutCommand(roomIndex); + } 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 75b1a9bf119..bf0c4c97b0d 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -11,5 +11,7 @@ public class CliSyntax { public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_GUEST = new Prefix("g/"); + public static final Prefix PREFIX_NRIC = new Prefix("id/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 522b93081cc..00000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea..cd023e6eeae 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -1,10 +1,11 @@ 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.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; 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_NRIC; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; @@ -32,14 +33,15 @@ public class EditCommandParser implements Parser { public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_NRIC, 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); + throw new ParseException(MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); @@ -52,6 +54,9 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); } + if (argMultimap.getValue(PREFIX_NRIC).isPresent()) { + editPersonDescriptor.setNric((ParserUtil.parseNric(argMultimap.getValue(PREFIX_NRIC).get()))); + } if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindGuestCommandParser.java similarity index 58% rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java rename to src/main/java/seedu/address/logic/parser/FindGuestCommandParser.java index 4fb71f23103..19a44acd115 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindGuestCommandParser.java @@ -4,30 +4,28 @@ import java.util.Arrays; -import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindGuestCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.NameContainsKeywordsPredicate; /** - * Parses input arguments and creates a new FindCommand object + * Parses input arguments and creates a new FindGuestCommand object */ -public class FindCommandParser implements Parser { - +public class FindGuestCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. + * Parses the given {@code String} of arguments in the context of the FindGuestCommand + * and returns a FindGuestCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ - public FindCommand parse(String args) throws ParseException { + public FindGuestCommand parse(String args) throws ParseException { String trimmedArgs = args.trim(); if (trimmedArgs.isEmpty()) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindGuestCommand.MESSAGE_USAGE)); } String[] nameKeywords = trimmedArgs.split("\\s+"); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + return new FindGuestCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); } - } diff --git a/src/main/java/seedu/address/logic/parser/FindRecordCommandParser.java b/src/main/java/seedu/address/logic/parser/FindRecordCommandParser.java new file mode 100644 index 00000000000..9bcc7639605 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindRecordCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import java.util.Arrays; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.FindRecordCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.residency.exceptions.ResidencyContainsKeywordsPredicate; + + +/** + * Parses input arguments and creates a new FindRecordCommand object. + */ +public class FindRecordCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindRecordCommand + * returns a FindRoomCommand object for execution. + * + * @param args takes in a String args in the context of the FindRecordCommand + * @return returns a FindRecordCommand object for execution + * @throws ParseException if the user input does not conform the expected format + */ + public FindRecordCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + FindRecordCommand.MESSAGE_USAGE)); + } + + String[] recordKeywords = trimmedArgs.split("\\s+"); + + return new FindRecordCommand(new ResidencyContainsKeywordsPredicate(Arrays.asList(recordKeywords))); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindRoomCommandParser.java b/src/main/java/seedu/address/logic/parser/FindRoomCommandParser.java new file mode 100644 index 00000000000..c0f8b66e316 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindRoomCommandParser.java @@ -0,0 +1,45 @@ +package seedu.address.logic.parser; + +import java.util.Arrays; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.util.StringUtil; +import seedu.address.logic.commands.FindRoomCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.room.RoomNumberContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindRoomCommand object. + */ +public class FindRoomCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindRoomCommand + * returns a FindRoomCommand object for execution. + * + * @param args takes in a String args in the context of the FindRoomCommand + * @return returns a FindRoomCommand object for execution + * @throws ParseException if the user input does not conform the expected format + */ + public FindRoomCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + FindRoomCommand.MESSAGE_USAGE)); + } + + String[] roomNumberKeywords = trimmedArgs.split("\\s+"); + + // checking validity of input + int numOfKeywords = roomNumberKeywords.length; + for (int i = 0; i < numOfKeywords; i++) { + String keyword = roomNumberKeywords[i]; + if (!StringUtil.isNonZeroUnsignedInteger(keyword) || keyword.length() != 3) { + throw new ParseException(String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, + FindRoomCommand.MESSAGE_INVALID_ROOM_NUMBER)); + } + } + + return new FindRoomCommand(new RoomNumberContainsKeywordsPredicate(Arrays.asList(roomNumberKeywords))); + } +} 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 00000000000..557e47dfd6c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.function.Predicate; + +import seedu.address.commons.core.listroomargs.ListRoomArg; +import seedu.address.commons.core.listtype.ListType; +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomIsOccupiedPredicate; +import seedu.address.model.room.RoomIsVacantPredicate; + +public class ListCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ListCommand + * and returns a ListCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public ListCommand parse(String args) throws ParseException { + // check for empty arguments + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + + String[] argsArr = trimmedArgs.split(" ", 2); + String type = argsArr[0]; + ListType listType = ParserUtil.parseListType(type); + if (argsArr.length == 1) { + return new ListCommand(listType); + } else { + // list guests command should not have extra arguments + if (listType.isGuestsType() || listType.isRecordsType()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + String listRoomArgument = argsArr[1]; + ListRoomArg arg = ParserUtil.parseListRoomArgument(listRoomArgument); + return new ListCommand(listType, getListRoomPredicate(arg)); + } + } + + /** + * Returns the Predicate corresponding to the {@code ListRoomArg}. + * + * @param argument + */ + public Predicate getListRoomPredicate(ListRoomArg argument) { + if (argument.isVacant()) { + return new RoomIsVacantPredicate(); + } else { + return new RoomIsOccupiedPredicate(); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..75c089c21a4 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -1,17 +1,21 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import java.util.Collection; import java.util.HashSet; import java.util.Set; import seedu.address.commons.core.index.Index; +import seedu.address.commons.core.listroomargs.ListRoomArg; +import seedu.address.commons.core.listtype.ListType; 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.Nric; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; @@ -95,6 +99,20 @@ public static Email parseEmail(String email) throws ParseException { return new Email(trimmedEmail); } + /** + * Parses a {@code String nric} into an {@code Nric}. + * + * @throws ParseException if the given {@code String nric} is invalid. + */ + public static Nric parseNric(String nric) throws ParseException { + requireNonNull(nric); + String trimmedNric = nric.trim(); + if (!Nric.isValidNric(trimmedNric)) { + throw new ParseException(Nric.MESSAGE_CONSTRAINTS); + } + return new Nric(trimmedNric); + } + /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. @@ -121,4 +139,50 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String list type} into a {@code ListType}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code list type} is invalid. + */ + public static ListType parseListType(String type) throws ParseException { + requireNonNull(type); + String trimmedType = type.trim(); + if (!ListType.isValidListType(trimmedType)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListType.MESSAGE_CONSTRAINTS)); + } + return new ListType(trimmedType); + } + + /** + * Parses a {@code String list room argument} into a {@code ListRoomArg}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code list room argument} is invalid. + */ + public static ListRoomArg parseListRoomArgument(String arg) throws ParseException { + requireNonNull(arg); + String trimmedArg = arg.trim(); + if (!ListRoomArg.isValidListType(trimmedArg)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListRoomArg.MESSAGE_CONSTRAINTS)); + } + return new ListRoomArg(trimmedArg); + } + + /** + * Parses {@code oneBasedIndex} into an {@code Integer} and returns it. + * Leading and trailing whitespaces will be trimmed. + * @throws ParseException if the specified integer is invalid (not non-zero unsigned integer). + */ + public static Integer parseNumber(String oneBasedIndex) throws ParseException { + String trimmedIndex = oneBasedIndex.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new ParseException(MESSAGE_INVALID_INDEX); + } + return Integer.parseInt(trimmedIndex); + } + } diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..2f3f2229dd2 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -3,18 +3,30 @@ import static java.util.Objects.requireNonNull; import java.util.List; +import java.util.Optional; +import java.util.Set; import javafx.collections.ObservableList; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.residency.ReadOnlyResidencyBook; +import seedu.address.model.residency.Residency; +import seedu.address.model.residency.ResidencyBook; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomList; /** * Wraps all data at the address-book level * Duplicates are not allowed (by .isSamePerson comparison) */ public class AddressBook implements ReadOnlyAddressBook { + public static final String MESSAGE_INVALID_ADDRESS_BOOK = + "The address book has a mismatch between a room's vacancy status and it's residency."; private final UniquePersonList persons; + private final RoomList rooms; + private final ResidencyBook residencyBook; + private final ResidencyBook recordsBook; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -25,6 +37,9 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + rooms = new RoomList(); + residencyBook = new ResidencyBook(false); + recordsBook = new ResidencyBook(true); } public AddressBook() {} @@ -47,6 +62,30 @@ public void setPersons(List persons) { this.persons.setPersons(persons); } + /** + * 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); + } + + /** + * Replaces the contents of the residency list with {@code residencies}. + * {@code residencies} must not contain duplicate residencies. + */ + public void setResidencies(List residencies) { + this.residencyBook.setResidencies(residencies); + } + + /** + * Replaces the contents of the residency records with {@code records}. + * {@code records} must not contain duplicate residencies. + */ + public void setRecords(List records) { + this.recordsBook.setResidencies(records); + } + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ @@ -54,9 +93,12 @@ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setPersons(newData.getPersonList()); + setRooms(newData.getRoomList()); + setResidencies(newData.getResidencyList()); + setRecords(newData.getRecordsList()); } - //// person-level operations + //// (person / room)-level operations /** * Returns true if a person with the same identity as {@code person} exists in the address book. @@ -66,6 +108,14 @@ public boolean hasPerson(Person person) { return persons.contains(person); } + /** + * Returns true if a room with the same identity as {@code room} exists in the address book. + */ + public boolean hasRoom(Room room) { + requireNonNull(room); + return rooms.contains(room); + } + /** * Adds a person to the address book. * The person must not already exist in the address book. @@ -74,6 +124,14 @@ public void addPerson(Person p) { persons.add(p); } + /** + * Adds a room to the address book. + * The room must not already exist in the address book. + */ + public void addRoom(Room r) { + rooms.add(r); + } + /** * Replaces the given person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the address book. @@ -81,8 +139,20 @@ public void addPerson(Person p) { */ public void setPerson(Person target, Person editedPerson) { requireNonNull(editedPerson); - persons.setPerson(target, editedPerson); + residencyBook.edit(target, editedPerson); + recordsBook.edit(target, editedPerson); + } + + /** + * Replaces the given room {@code target} with {@code editedRoom}. + * {@code target} must exist in the address book. + * The room identity of {@code editedRoom} must not be the same as another existing room in the address book. + */ + public void setRoom(Room target, Room editedRoom) { + requireNonNull(editedRoom); + + rooms.setRoom(target, editedRoom); } /** @@ -93,8 +163,74 @@ public void removePerson(Person key) { persons.remove(key); } + //// residency-level operations + + /** + * Registers a residency using a Room object and guests as a Set of person objects. + * + * @param room Room object. + * @param guests Set of Person objects residing in the room. + */ + public void register(Room room, Set guests) { + requireNonNull(room); + requireNonNull(guests); + residencyBook.register(room, guests); + } + + /** + * Registers a residency using a Residency object. + * + * @param residency Residency object that contains a Room object and guests as a Set of person objects. + */ + public void register(Residency residency) { + requireNonNull(residency); + residencyBook.register(residency); + } + + /** + * Registers a residency into the recordsBook using a Residency object. + * + * @param residency Residency object that contains a Room object and guests as a Set of person objects. + */ + public void record(Residency residency) { + requireNonNull(residency); + recordsBook.register(residency); + } + + public void removeResidency(Residency residency) { + residencyBook.remove(residency); + } + + public Optional getResidency(Room room) { + return residencyBook.getResidency(room); + } + + public Optional getResidency(Person person) { + return residencyBook.getResidency(person); + } + + public Optional getRecord(Room room) { + return recordsBook.getResidency(room); + } + + public Optional getRecord(Person person) { + return recordsBook.getResidency(person); + } + //// util methods + /** + * Returns true if the vacancy status of all rooms match their recorded statuses in the residency book. + */ + public boolean isValid() { + for (Room room : rooms) { + if (residencyBook.getResidency(room).isEmpty() != room.isVacant()) { + return false; + } + } + return true; + } + @Override public String toString() { return persons.asUnmodifiableObservableList().size() + " persons"; @@ -106,11 +242,40 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + @Override + public ObservableList getRoomList() { + return rooms.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getResidencyList() { + return residencyBook.asUnmodifiableObservableList(); + } + + @Override + public ReadOnlyResidencyBook getResidencyBook() { + return residencyBook; + } + + @Override + public ObservableList getRecordsList() { + return recordsBook.asUnmodifiableObservableList(); + } + + @Override + public ReadOnlyResidencyBook getRecordsBook() { + return recordsBook; + } + + @SuppressWarnings("checkstyle:CommentsIndentation") @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)); + && persons.equals(((AddressBook) other).persons) + && rooms.equals(((AddressBook) other).rooms) + && residencyBook.equals(((AddressBook) other).residencyBook) + && recordsBook.equals(((AddressBook) other).recordsBook)); } @Override diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..fa15437a4d0 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,11 +1,18 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.model.person.Person; +import seedu.address.model.residency.Residency; +import seedu.address.model.residency.exceptions.DuplicatePersonRegException; +import seedu.address.model.residency.exceptions.DuplicateRoomRegException; +import seedu.address.model.residency.exceptions.ResidencyNotFoundException; +import seedu.address.model.room.Room; /** * The API of the Model component. @@ -13,6 +20,8 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_ROOMS = unused -> true; + Predicate PREDICATE_SHOW_ALL_RECORDS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -57,6 +66,11 @@ public interface Model { */ boolean hasPerson(Person person); + /** + * Returns true if a room with the same room number as {@code room} exists in the address book. + */ + boolean hasRoom(Room room); + /** * Deletes the given person. * The person must exist in the address book. @@ -76,12 +90,90 @@ public interface Model { */ void setPerson(Person target, Person editedPerson); + /** + * Replaces the given room {@code target} with {@code editedRoom}. + * {@code target} must exist in the address book. + * The room identity of {@code editedRoom} must not be the same as another existing room in the address book. + */ + void setRoom(Room target, Room editedRoom); + + /** + * Registers the residency of a set of guests in a room. + * + * @param room The {@code Room} to stay in + * @param guests The {@code Set} of {@code Person}s to stay in the room + * @throws DuplicateRoomRegException if the {@code Room} is already registered. + * @throws DuplicatePersonRegException if any {@code Person} is already registered. + */ + void register(Room room, Set guests); + + /** + * Removes and de-registers a {@code Residency}, making the room and guests + * available for new registrations. + * + * @throws ResidencyNotFoundException if the given residency is not registered. + */ + void removeResidency(Residency residency); + + /** + * Gets the {@code Residency} containing this {@code Room}, if it exists. + * + * @param room The room to get the residency of + * @return An {@code Optional} with the {@code Residency} present if it exists, + * otherwise an empty Optional + */ + Optional getResidency(Room room); + + /** + * Gets the {@code Residency} containing this {@code Person}, if it exists. + * + * @param guest The {@code Person} to get the residency of + * @return An {@code Optional} with the {@code Residency} present if it exists, + * otherwise an empty Optional + */ + Optional getResidency(Person guest); + /** Returns an unmodifiable view of the filtered person list */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered room list */ + ObservableList getFilteredRoomList(); + + /** Returns the number of rooms in the filtered room list */ + Integer getNumberOfRooms(); + + /** Returns an unmodifiable view of the filtered record list */ + ObservableList getFilteredRecordList(); + /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** + * 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); + + /** + * Updates the filter of the filtered record list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredRecordList(Predicate predicate); + + /** + * Adds the given room. + * {@code room} must not already exist in the address book. + */ + void addRoom(Room room); + + /** + * Records the past residency of a set of guests in a room. + * + * @param residency The residency to be recorded. + */ + void record(Residency residency); + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 0650c954f5c..9bbcbcf8b67 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,6 +4,8 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; import java.util.logging.Logger; @@ -12,16 +14,21 @@ import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.model.person.Person; +import seedu.address.model.residency.Residency; +import seedu.address.model.room.Room; /** * Represents the in-memory model of the address book data. */ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); + private static Model modelInstance; private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final FilteredList filteredRooms; + private final FilteredList filteredRecords; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -35,6 +42,8 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredRooms = new FilteredList<>(this.addressBook.getRoomList()); + filteredRecords = new FilteredList<>(this.addressBook.getRecordsList()); } public ModelManager() { @@ -94,6 +103,12 @@ public boolean hasPerson(Person person) { return addressBook.hasPerson(person); } + @Override + public boolean hasRoom(Room room) { + requireNonNull(room); + return addressBook.hasRoom(room); + } + @Override public void deletePerson(Person target) { addressBook.removePerson(target); @@ -112,6 +127,47 @@ public void setPerson(Person target, Person editedPerson) { addressBook.setPerson(target, editedPerson); } + @Override + public void setRoom(Room target, Room editedRoom) { + requireAllNonNull(target, editedRoom); + + addressBook.setRoom(target, editedRoom); + } + + @Override + public void addRoom(Room room) { + addressBook.addRoom(room); + updateFilteredRoomList(PREDICATE_SHOW_ALL_ROOMS); + } + + public void register(Room room, Set guests) { + addressBook.register(room, guests); + } + + public void removeResidency(Residency residency) { + addressBook.removeResidency(residency); + } + + public Optional getResidency(Room room) { + return addressBook.getResidency(room); + } + + public Optional getResidency(Person guest) { + return addressBook.getResidency(guest); + } + + public void record(Residency residency) { + addressBook.record(residency); + } + + public Optional getRecord(Room room) { + return addressBook.getRecord(room); + } + + public Optional getRecord(Person guest) { + return addressBook.getRecord(guest); + } + //=========== Filtered Person List Accessors ============================================================= /** @@ -129,6 +185,48 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + //=========== Filtered Room List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Room} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredRoomList() { + return filteredRooms; + } + + /** + * Returns the number of rooms in the room list. + */ + public Integer getNumberOfRooms() { + return filteredRooms.size(); + } + + @Override + public void updateFilteredRoomList(Predicate predicate) { + requireNonNull(predicate); + filteredRooms.setPredicate(predicate); + } + + + //=========== Filtered Record List Accessors ============================================================= + /** + * Returns an unmodifiable view of the list of {@code Residency} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredRecordList() { + return filteredRecords; + } + + @Override + public void updateFilteredRecordList(Predicate predicate) { + requireNonNull(predicate); + filteredRecords.setPredicate(predicate); + } + + @Override public boolean equals(Object obj) { // short circuit if same object @@ -148,4 +246,12 @@ public boolean equals(Object obj) { && filteredPersons.equals(other.filteredPersons); } + public static void setInstance(Model model) { + modelInstance = model; + } + + public static Model getInstance() { + return modelInstance; + } + } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..5fc4edc7d45 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -2,6 +2,9 @@ import javafx.collections.ObservableList; import seedu.address.model.person.Person; +import seedu.address.model.residency.ReadOnlyResidencyBook; +import seedu.address.model.residency.Residency; +import seedu.address.model.room.Room; /** * Unmodifiable view of an address book @@ -14,4 +17,33 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + /** + * Returns an unmodifiable view of the rooms list. + * This list will not contain any duplicate rooms. + */ + ObservableList getRoomList(); + + /** + * Returns an unmodifiable view of the residencies list. + * This list will not contain any duplicate residencies. + */ + ObservableList getResidencyList(); + + /** + * Returns an unmodifiable view of the residency book. + * This will not contain any duplicate residencies. + */ + ReadOnlyResidencyBook getResidencyBook(); + + /** + * Returns an unmodifiable view of the residencies records list. + * This list will not contain any duplicate residencies. + */ + ObservableList getRecordsList(); + + /** + * Returns an unmodifiable view of the residency records book. + * This will not contain any duplicate residencies. + */ + ReadOnlyResidencyBook getRecordsBook(); } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 25a5fd6eab9..d14dc48a49a 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data" , "trace2gather.json"); /** * Creates a {@code UserPrefs} with default values. diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 60472ca22a0..1f97df37acc 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -9,13 +9,15 @@ */ public class Address { - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; + public static final String MESSAGE_CONSTRAINTS = + "Addresses can take any values of length 50 or less, 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 VALIDATION_REGEX = "[^\\s].*"; + public static final int MAX_LENGTH = 100; public final String value; @@ -34,7 +36,7 @@ public Address(String address) { * Returns true if a given string is a valid email. */ public static boolean isValidAddress(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && test.length() <= MAX_LENGTH; } @Override diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index f866e7133de..6c9c9d088f1 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -9,6 +9,7 @@ */ public class Email { + public static final int MAX_LENGTH = 50; private static final String SPECIAL_CHARACTERS = "+_.-"; public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " + "and adhere to the following constraints:\n" @@ -20,7 +21,8 @@ public class Email { + "The domain name must:\n" + " - end with a domain label at least 2 characters long\n" + " - have each domain label start and end with alphanumeric characters\n" - + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any."; + + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any.\n" + + "3. The total length must not exceed 50 characters."; // alphanumeric and special characters private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]" @@ -48,7 +50,7 @@ public Email(String email) { * Returns if a given string is a valid email. */ public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && test.length() <= MAX_LENGTH; } @Override diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 79244d71cf7..9b102f77a4e 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -10,13 +10,15 @@ public class Name { public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Names should only contain up to 50 alphanumeric characters (including spaces), " + + "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 VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final int MAX_LENGTH = 50; public final String fullName; @@ -35,7 +37,7 @@ public Name(String name) { * Returns true if a given string is a valid name. */ public static boolean isValidName(String test) { - return test.matches(VALIDATION_REGEX); + return test.matches(VALIDATION_REGEX) && test.length() <= MAX_LENGTH; } diff --git a/src/main/java/seedu/address/model/person/Nric.java b/src/main/java/seedu/address/model/person/Nric.java new file mode 100644 index 00000000000..1c328ee4dd5 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Nric.java @@ -0,0 +1,79 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * The ID field of a person. Used to distinguish between different Persons, + * especially if they have the same name. + */ +public class Nric { + + public static final String MESSAGE_CONSTRAINTS = + "IDs must have a length of less than 50"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final int MAX_LENGTH = 50; + + public final String value; + + /** + * Constructor for Nric object. + */ + public Nric(String ic) { + this.value = ic; + } + + /** + * Constructs an Id object using given Integer Id. + * + * @param ic the Nric of the guest, stored as a String. + * @return a unique Nric object. + */ + public static Nric of(String ic) { + requireNonNull(ic); + checkArgument(isValidNric(ic), MESSAGE_CONSTRAINTS); + return new Nric(ic); + } + + /** + * Evaluates if an Nric provided is valid to be a Person's Nric. + * In other words, it checks for null or whether the string is empty. + * + * @param test the String of a supposed Nric. + * @return boolean to indicate if string is non-empty. + */ + public static boolean isValidNric(String test) { + return test.matches(VALIDATION_REGEX) && test.length() <= MAX_LENGTH; + } + + /** + * Returns the Nric object's value. + * @return the String that the Nric object contains. + */ + public String getValue() { + return this.value; + } + + @Override + public String toString() { + return this.value; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Nric + && ((Nric) other).value.toLowerCase().equals(this.value.toLowerCase())); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 8ff1d83fe89..06c7ff21da3 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -17,6 +17,7 @@ public class Person { // Identity fields private final Name name; + private final Nric nric; private final Phone phone; private final Email email; @@ -27,15 +28,24 @@ public class Person { /** * 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); + public Person(Name name, Phone phone, Email email, Address address, Nric nric, Set tags) { + requireAllNonNull(name, phone, email, address, nric, tags); this.name = name; this.phone = phone; this.email = email; this.address = address; + this.nric = nric; this.tags.addAll(tags); } + /** + * Creates a new {@code Person} with the specified data fields, and generates a new {@code Id} for them. + * Every field must be present and not null. + */ + public static Person createPerson(Name name, Phone phone, Email email, Address address, Nric nric, Set tags) { + return new Person(name, phone, email, address, nric, tags); + } + public Name getName() { return name; } @@ -52,6 +62,10 @@ public Address getAddress() { return address; } + public Nric getNric() { + return nric; + } + /** * Returns an immutable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. @@ -61,8 +75,7 @@ public Set getTags() { } /** - * Returns true if both persons have the same name. - * This defines a weaker notion of equality between two persons. + * Returns true if both persons have the same nric. */ public boolean isSamePerson(Person otherPerson) { if (otherPerson == this) { @@ -70,7 +83,7 @@ public boolean isSamePerson(Person otherPerson) { } return otherPerson != null - && otherPerson.getName().equals(getName()); + && otherPerson.getNric().equals(getNric()); } /** @@ -88,17 +101,22 @@ public boolean equals(Object other) { } Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) + return otherPerson.getNric().equals(getNric()); + + /* + otherPerson.getName().equals(getName()) && otherPerson.getPhone().equals(getPhone()) && otherPerson.getEmail().equals(getEmail()) && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); + && 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); + //return Objects.hash(name, phone, email, address, tags, id); + return Objects.hash(name, phone, email, address, nric, tags); } @Override @@ -110,7 +128,9 @@ public String toString() { .append("; Email: ") .append(getEmail()) .append("; Address: ") - .append(getAddress()); + .append(getAddress()) + .append("; Id: ") + .append(getNric()); Set tags = getTags(); if (!tags.isEmpty()) { diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index 872c76b382f..9891f524d5a 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -11,8 +11,8 @@ public class Phone { public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; + "Phone numbers should only contain numbers, and it should be 3-15 digits long"; + public static final String VALIDATION_REGEX = "\\d{3,15}"; public final String value; /** diff --git a/src/main/java/seedu/address/model/residency/ReadOnlyResidencyBook.java b/src/main/java/seedu/address/model/residency/ReadOnlyResidencyBook.java new file mode 100644 index 00000000000..232cbb4ce82 --- /dev/null +++ b/src/main/java/seedu/address/model/residency/ReadOnlyResidencyBook.java @@ -0,0 +1,27 @@ +package seedu.address.model.residency; + +import java.util.Map; + +import javafx.collections.ObservableList; +import seedu.address.model.person.Person; +import seedu.address.model.room.Room; + +/** + * Unmodifiable view of a residency book + */ +public interface ReadOnlyResidencyBook { + + /** + * Returns an unmodifiable view of the person to residency map. + * This map will not contain any duplicate registrations. + */ + Map getGuestMap(); + + /** + * Returns an unmodifiable view of the room to residency map. + * This map will not contain any duplicate registrations. + */ + Map getRoomMap(); + + ObservableList asUnmodifiableObservableList(); +} diff --git a/src/main/java/seedu/address/model/residency/Residency.java b/src/main/java/seedu/address/model/residency/Residency.java new file mode 100644 index 00000000000..55b27b837d1 --- /dev/null +++ b/src/main/java/seedu/address/model/residency/Residency.java @@ -0,0 +1,145 @@ +package seedu.address.model.residency; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import seedu.address.model.person.Person; +import seedu.address.model.residency.exceptions.AlreadyCheckedOutException; +import seedu.address.model.residency.exceptions.CheckOutBeforeCheckInException; +import seedu.address.model.room.Room; + +/** + * Encapsulates the stay of a guest in a room. + */ +public class Residency { + + private final Room room; + private final Set guests; + private LocalDateTime checkInTime; + private LocalDateTime checkOutTime; + + /** + * Constructor for Residency object. + * Automatically sets the check in time to the current time. + * + * @param room Room object where guests stay in. + * @param guests Set of Person objects who stayed in this room. + */ + public Residency(Room room, Set guests) { + this(room, guests, LocalDateTime.now(), null); + } + + /** + * Constructor for Residency object. + * Allows for setting a custom check in time. + * + * @param room Room object where guests stay in. + * @param guests Set of Person objects who stayed in this room. + */ + public Residency(Room room, Set guests, LocalDateTime checkInTime, LocalDateTime checkOutTime) { + requireNonNull(room); + requireAllNonNull(guests); + this.room = room; + this.guests = guests; + this.checkInTime = checkInTime; + this.checkOutTime = checkOutTime; + } + + /** + * Returns the Room object in this Residency object. + * @return Room object. + */ + public Room getRoom() { + return room; + } + + /** + * Returns the Set of Person objects in this Residency object. + * + * @return An unmodifiable set of guests. + */ + public Set getGuests() { + return Collections.unmodifiableSet(guests); + } + + /** + * Replaces a guest object with its updated Person object. + * + * @param personToEdit The Person object to replace + * @param editedPerson The Person object to replace with + */ + public void setGuest(Person personToEdit, Person editedPerson) { + guests.remove(personToEdit); + guests.add(editedPerson); + } + + /** + * Sets the check out time to the current time. + * This cannot be changed in the future. + * + * @throws AlreadyCheckedOutException if this method has been called before. + */ + public void checkOut() { + if (checkOutTime != null) { + throw new AlreadyCheckedOutException(); + } + LocalDateTime currentTime = LocalDateTime.now(); + if (currentTime.isBefore(checkInTime)) { + throw new CheckOutBeforeCheckInException(); + } + checkOutTime = currentTime; + } + + public LocalDateTime getCheckInTime() { + return this.checkInTime; + } + + public Optional getCheckOutTime() { + return Optional.ofNullable(this.checkOutTime); + } + + public boolean isCheckedIn() { + return checkOutTime == null; + } + + @Override + public String toString() { + String message = String.format("Room [%s], Guests [%s], CheckInTime [%s], CheckOutTime [%s]", + room, guests, checkInTime.truncatedTo(ChronoUnit.DAYS), checkOutTime.truncatedTo(ChronoUnit.DAYS)); + return message; + } + + @Override + public boolean equals(Object other) { + if (this.checkOutTime == null) { + return other == this + || (other instanceof Residency + && ((Residency) other).room.equals(this.room) + && ((Residency) other).guests.equals(this.guests) + && ((Residency) other).checkInTime.truncatedTo(ChronoUnit.MINUTES) + .equals(this.checkInTime.truncatedTo(ChronoUnit.MINUTES))); + } else { + return other == this + || (other instanceof Residency + && ((Residency) other).room.equals(this.room) + && ((Residency) other).guests.equals(this.guests) + && ((Residency) other).checkInTime.truncatedTo(ChronoUnit.MINUTES) + .equals(this.checkInTime.truncatedTo(ChronoUnit.MINUTES)) + && ((Residency) other).checkOutTime.truncatedTo(ChronoUnit.MINUTES) + .equals(this.checkOutTime.truncatedTo(ChronoUnit.MINUTES))); + } + + } + + @Override + public int hashCode() { + return Objects.hash(room, guests, checkInTime, checkOutTime); + } +} diff --git a/src/main/java/seedu/address/model/residency/ResidencyBook.java b/src/main/java/seedu/address/model/residency/ResidencyBook.java new file mode 100644 index 00000000000..dbd5950f886 --- /dev/null +++ b/src/main/java/seedu/address/model/residency/ResidencyBook.java @@ -0,0 +1,191 @@ +package seedu.address.model.residency; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.person.Person; +import seedu.address.model.residency.exceptions.DuplicatePersonRegException; +import seedu.address.model.residency.exceptions.DuplicateRoomRegException; +import seedu.address.model.residency.exceptions.ResidencyNotFoundException; +import seedu.address.model.room.Room; + +public class ResidencyBook implements ReadOnlyResidencyBook { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + private final HashMap roomMap = new HashMap<>(); + private final HashMap guestMap = new HashMap<>(); + private final boolean allowDuplicates; + + public ResidencyBook(boolean allowDuplicates) { + this.allowDuplicates = allowDuplicates; + } + + /** + * Registers the residency of a set of guests in a room. + * + * @param room The {@code Room} to stay in + * @param guests The {@code Set} of {@code Person}s to stay in the room + * @throws DuplicateRoomRegException if the {@code Room} is already registered. + * @throws DuplicatePersonRegException if any {@code Person} is already registered. + */ + public void register(Room room, Set guests) { + requireNonNull(room); + requireAllNonNull(guests); + + register(new Residency(room, guests)); + } + + /** + * Registers a {@code Residency}. + */ + public void register(Residency residency) { + requireNonNull(residency); + Room room = residency.getRoom(); + Set guests = residency.getGuests(); + + if (roomMap.containsKey(room) && !allowDuplicates) { + throw new DuplicateRoomRegException(); + } + for (Person guest : guests) { + if (guestMap.containsKey(guest) && !allowDuplicates) { + throw new DuplicatePersonRegException(guest); + } + } + + internalList.add(residency); + roomMap.put(room, residency); + for (Person guest : guests) { + guestMap.put(guest, residency); + } + } + + /** + * Removes and de-registers a {@code Residency}, making the room and guests + * available for new registrations. + * + * @throws ResidencyNotFoundException if the given residency is not registered. + */ + public void remove(Residency residency) { + requireNonNull(residency); + Room room = residency.getRoom(); + Set guests = residency.getGuests(); + + if (!internalList.contains(residency)) { + throw new ResidencyNotFoundException(); + } + + internalList.remove(residency); + roomMap.remove(room); + for (Person guest : guests) { + guestMap.remove(guest); + } + } + + /** + * Replaces a {@code Person} object with a new one in the relevant {@code Residency}, + * if it exists, and updates the registries accordingly. + * + * @param personToEdit The Person object to replace + * @param editedPerson The Person object to replace with + */ + public void edit(Person personToEdit, Person editedPerson) { + requireAllNonNull(personToEdit, editedPerson); + List residencies = getResidencies(personToEdit); + + for (Residency residency : residencies) { + remove(residency); + residency.setGuest(personToEdit, editedPerson); + register(residency); + } + } + + /** + * Gets the {@code Residency} containing this {@code Room}, if it exists. + * + * @param room The room to get the residency of + * @return An {@code Optional} with the {@code Residency} present if it exists, + * otherwise an empty Optional + */ + public Optional getResidency(Room room) { + requireNonNull(room); + return Optional.ofNullable(roomMap.get(room)); + } + + /** + * Gets the {@code Residency} containing this {@code Person}, if it exists. + * + * @param guest The {@code Person} to get the residency of + * @return An {@code Optional} with the {@code Residency} present if it exists, + * otherwise an empty Optional + */ + public Optional getResidency(Person guest) { + requireNonNull(guest); + return Optional.ofNullable(guestMap.get(guest)); + } + + /** + * Gets the List of {@code Residency} containing this {@code Person}, if it exists. + * + * @param guest The {@code Person} to get the residency of + * @return A {@code List} with the {@code Residency} that the {@code Person} is in. + */ + public List getResidencies(Person guest) { + requireNonNull(guest); + List relevantResidencies = internalList.stream() + .filter(residency -> residency.getGuests().contains(guest)).collect(Collectors.toUnmodifiableList()); + return relevantResidencies; + } + + public void setResidencies(List residencies) { + internalList.clear(); + guestMap.clear(); + roomMap.clear(); + for (Residency residency : residencies) { + register(residency); + } + } + + @Override + public Map getGuestMap() { + return Collections.unmodifiableMap(guestMap); + } + + @Override + public Map getRoomMap() { + return Collections.unmodifiableMap(roomMap); + } + + @Override + public ObservableList asUnmodifiableObservableList() { + Comparator cmp = new Comparator() { + @Override + public int compare(Residency o1, Residency o2) { + return o2.getCheckInTime().compareTo(o1.getCheckInTime()); + } + }; + FXCollections.sort(internalList, cmp); + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ResidencyBook // instanceof handles nulls + && allowDuplicates == ((ResidencyBook) other).allowDuplicates + && internalList.equals(((ResidencyBook) other).internalList)); + } +} diff --git a/src/main/java/seedu/address/model/residency/exceptions/AlreadyCheckedOutException.java b/src/main/java/seedu/address/model/residency/exceptions/AlreadyCheckedOutException.java new file mode 100644 index 00000000000..b6e6756b996 --- /dev/null +++ b/src/main/java/seedu/address/model/residency/exceptions/AlreadyCheckedOutException.java @@ -0,0 +1,7 @@ +package seedu.address.model.residency.exceptions; + +public class AlreadyCheckedOutException extends RuntimeException { + public AlreadyCheckedOutException() { + super("This Residency has already been checked out."); + } +} diff --git a/src/main/java/seedu/address/model/residency/exceptions/CheckOutBeforeCheckInException.java b/src/main/java/seedu/address/model/residency/exceptions/CheckOutBeforeCheckInException.java new file mode 100644 index 00000000000..0510d4a252e --- /dev/null +++ b/src/main/java/seedu/address/model/residency/exceptions/CheckOutBeforeCheckInException.java @@ -0,0 +1,7 @@ +package seedu.address.model.residency.exceptions; + +public class CheckOutBeforeCheckInException extends RuntimeException { + public CheckOutBeforeCheckInException() { + super("The check-out time of this residency would be before the check-in time!"); + } +} diff --git a/src/main/java/seedu/address/model/residency/exceptions/DuplicatePersonRegException.java b/src/main/java/seedu/address/model/residency/exceptions/DuplicatePersonRegException.java new file mode 100644 index 00000000000..f6bab730f41 --- /dev/null +++ b/src/main/java/seedu/address/model/residency/exceptions/DuplicatePersonRegException.java @@ -0,0 +1,9 @@ +package seedu.address.model.residency.exceptions; + +import seedu.address.model.person.Person; + +public class DuplicatePersonRegException extends RuntimeException { + public DuplicatePersonRegException(Person person) { + super(person.getName() + " is already checked into another room."); + } +} diff --git a/src/main/java/seedu/address/model/residency/exceptions/DuplicateRoomRegException.java b/src/main/java/seedu/address/model/residency/exceptions/DuplicateRoomRegException.java new file mode 100644 index 00000000000..c683f6dac71 --- /dev/null +++ b/src/main/java/seedu/address/model/residency/exceptions/DuplicateRoomRegException.java @@ -0,0 +1,8 @@ +package seedu.address.model.residency.exceptions; + +public class DuplicateRoomRegException extends RuntimeException { + public static final String MESSAGE = "This room is currently occupied."; + public DuplicateRoomRegException() { + super(MESSAGE); + } +} diff --git a/src/main/java/seedu/address/model/residency/exceptions/ResidencyContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/residency/exceptions/ResidencyContainsKeywordsPredicate.java new file mode 100644 index 00000000000..25c7a1854e9 --- /dev/null +++ b/src/main/java/seedu/address/model/residency/exceptions/ResidencyContainsKeywordsPredicate.java @@ -0,0 +1,29 @@ +package seedu.address.model.residency.exceptions; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.model.residency.Residency; + +public class ResidencyContainsKeywordsPredicate implements Predicate { + + private final List keywords; + + public ResidencyContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Residency residency) { + return keywords.stream() + .map(keyword -> keyword.toLowerCase()) + .allMatch(keyword -> residency.toString().toLowerCase().contains(keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ResidencyContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((ResidencyContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/residency/exceptions/ResidencyNotFoundException.java b/src/main/java/seedu/address/model/residency/exceptions/ResidencyNotFoundException.java new file mode 100644 index 00000000000..ffda34cc0a7 --- /dev/null +++ b/src/main/java/seedu/address/model/residency/exceptions/ResidencyNotFoundException.java @@ -0,0 +1,4 @@ +package seedu.address.model.residency.exceptions; + +public class ResidencyNotFoundException extends RuntimeException { +} 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 00000000000..f6aec7388f1 --- /dev/null +++ b/src/main/java/seedu/address/model/room/Room.java @@ -0,0 +1,91 @@ +package seedu.address.model.room; + +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; + +public class Room { + private final RoomNumber roomNumber; + private final Vacancy vacancy; + private final Set tags = new HashSet<>(); + + /** + * Constructs a {@code Room}. + * + * @param roomNumber A valid room number. + */ + public Room(RoomNumber roomNumber, Set tags) { + this.roomNumber = roomNumber; + vacancy = Vacancy.VACANT; + this.tags.addAll(tags); + } + + /** + * Constructs a Room given its roomNumber, vacancy, status and list of guests. + * + * @param roomNumber RoomNumber roomNumber + * @param vacancy Vacant if room has no guests. + */ + public Room(RoomNumber roomNumber, Vacancy vacancy, Set tags) { + requireAllNonNull(roomNumber, vacancy, tags); + this.roomNumber = roomNumber; + this.vacancy = vacancy; + this.tags.addAll(tags); + } + + public RoomNumber getRoomNumber() { + return roomNumber; + } + + public Vacancy getVacancy() { + return this.vacancy; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + public boolean isVacant() { + return vacancy.isVacant(); + } + + /** + * Returns true if both rooms have the same {@code RoomNumber}. + */ + public boolean isSameRoom(Room otherRoom) { + if (otherRoom == this) { + return true; + } + + return otherRoom != null + && otherRoom.getRoomNumber().equals(getRoomNumber()); + } + + @Override + public String toString() { + return roomNumber.toString(); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Room + && roomNumber.equals(((Room) other).roomNumber) + && vacancy.equals(((Room) other).vacancy) + && tags.equals(((Room) other).tags)); + } + + @Override + public int hashCode() { + return Objects.hash(roomNumber, vacancy); + } +} diff --git a/src/main/java/seedu/address/model/room/RoomIsOccupiedPredicate.java b/src/main/java/seedu/address/model/room/RoomIsOccupiedPredicate.java new file mode 100644 index 00000000000..514bbdcae31 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomIsOccupiedPredicate.java @@ -0,0 +1,19 @@ +package seedu.address.model.room; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Room}'s {@code Vacancy} status is occupied. + */ +public class RoomIsOccupiedPredicate implements Predicate { + + @Override + public boolean test(Room room) { + return !room.isVacant(); + } + + @Override + public boolean equals(Object other) { + return (other instanceof RoomIsOccupiedPredicate); + } +} diff --git a/src/main/java/seedu/address/model/room/RoomIsVacantPredicate.java b/src/main/java/seedu/address/model/room/RoomIsVacantPredicate.java new file mode 100644 index 00000000000..34750423906 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomIsVacantPredicate.java @@ -0,0 +1,19 @@ +package seedu.address.model.room; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Room}'s {@code Vacancy} status is vacant. + */ +public class RoomIsVacantPredicate implements Predicate { + + @Override + public boolean test(Room room) { + return room.isVacant(); + } + + @Override + public boolean equals(Object other) { + return (other instanceof RoomIsVacantPredicate); + } +} diff --git a/src/main/java/seedu/address/model/room/RoomList.java b/src/main/java/seedu/address/model/room/RoomList.java new file mode 100644 index 00000000000..e4496553ab5 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomList.java @@ -0,0 +1,126 @@ +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 javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.room.exceptions.DuplicateRoomException; +import seedu.address.model.room.exceptions.RoomNotFoundException; + +public class RoomList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Adds a room to the list. + * The room must not already exist in the list. + */ + public void add(Room toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateRoomException(); + } + + internalList.add(toAdd); + } + + /** + * 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); + } + + + /** + * Removes the equivalent room from the list. + * The room must exist in the list. + */ + public void remove(Room toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new RoomNotFoundException(); + } + } + + public void setRooms(RoomList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code rooms}. + * {@code rooms} must not contain duplicate persons. + */ + public void setRooms(List rooms) { + requireAllNonNull(rooms); + if (!roomsAreUnique(rooms)) { + throw new DuplicateRoomException(); + } + internalList.setAll(rooms); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + /** + * 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); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RoomList // instanceof handles nulls + && internalList.equals(((RoomList) other).internalList)); + } + + @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/RoomNumber.java b/src/main/java/seedu/address/model/room/RoomNumber.java new file mode 100644 index 00000000000..4a1a1024072 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomNumber.java @@ -0,0 +1,49 @@ +package seedu.address.model.room; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +public class RoomNumber { + + + public static final String MESSAGE_CONSTRAINTS = + "Room numbers should only contain numbers, and it must be 3 digits long"; + public static final String VALIDATION_REGEX = "\\d{3}"; + public final String value; + + /** + * Constructs a {@code RoomNumber}. + * + * @param roomNumber A valid room number. + */ + public RoomNumber(String roomNumber) { + requireNonNull(roomNumber); + checkArgument(isValidRoomNumber(roomNumber), MESSAGE_CONSTRAINTS); + this.value = roomNumber; + } + + /** + * Returns true if a given string is a valid room number. + */ + public static boolean isValidRoomNumber(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof 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/RoomNumberContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/room/RoomNumberContainsKeywordsPredicate.java new file mode 100644 index 00000000000..ae5d08a1e69 --- /dev/null +++ b/src/main/java/seedu/address/model/room/RoomNumberContainsKeywordsPredicate.java @@ -0,0 +1,30 @@ +package seedu.address.model.room; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Room}'s {@code RoomNumber} matches any of the keywords given. + */ +public class RoomNumberContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public RoomNumberContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Room room) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(room.getRoomNumber().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RoomNumberContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((RoomNumberContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/room/Vacancy.java b/src/main/java/seedu/address/model/room/Vacancy.java new file mode 100644 index 00000000000..1ef7661bc4b --- /dev/null +++ b/src/main/java/seedu/address/model/room/Vacancy.java @@ -0,0 +1,58 @@ +package seedu.address.model.room; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +public abstract class Vacancy { + + public static final Vacancy VACANT = new Vacant(); + public static final Vacancy OCCUPIED = new Occupied(); + + /** + * Returns a {@code Vacancy} of either vacant or occupied. + */ + public static Vacancy of(boolean isVacant) { + requireAllNonNull(isVacant); + return isVacant + ? VACANT + : OCCUPIED; + } + + public abstract boolean isVacant(); + + private static class Vacant extends Vacancy { + + @Override + public boolean isVacant() { + return true; + } + + @Override + public String toString() { + return "Vacant"; + } + + @Override + public boolean equals(Object other) { + return other instanceof Vacant; + } + } + + private static class Occupied extends Vacancy { + + @Override + public boolean isVacant() { + return false; + } + + @Override + public String toString() { + return "Occupied"; + } + + @Override + public boolean equals(Object other) { + return other instanceof Occupied; + } + } + +} 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 00000000000..c71604a7339 --- /dev/null +++ b/src/main/java/seedu/address/model/room/exceptions/DuplicateRoomException.java @@ -0,0 +1,11 @@ +package seedu.address.model.room.exceptions; + +/** + * Signals that the operation will result in duplicate Rooms (Rooms are considered duplicates if they have the same + * identity). + */ +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/RoomNotFoundException.java b/src/main/java/seedu/address/model/room/exceptions/RoomNotFoundException.java new file mode 100644 index 00000000000..1d44976f2fa --- /dev/null +++ b/src/main/java/seedu/address/model/room/exceptions/RoomNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.room.exceptions; + +/** + * Signals that the operation is unable to find the specified room. + */ +public class RoomNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..b97c69c4ab7 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,5 +1,6 @@ package seedu.address.model.util; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; @@ -9,34 +10,40 @@ import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.residency.Residency; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomNumber; import seedu.address.model.tag.Tag; /** * Contains utility methods for populating {@code AddressBook} 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")) + Person.createPerson(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Address("Blk 30 Geylang Street 29, #06-40"), new Nric("S9678901C"), + Set.of()), + Person.createPerson(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new Nric("S9876543A"), + Set.of()), + Person.createPerson(new Name("Charlotte Oliveiro"), new Phone("93210283"), + new Email("charlotte@example.com"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), new Nric("000-00-0001"), + Set.of()), + Person.createPerson(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new Nric("S9012345B"), + Set.of()), + Person.createPerson(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), + new Address("Blk 47 Tampines Street 20, #17-35"), new Nric("000-00-0002"), + Set.of()), + Person.createPerson(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), + new Address("Blk 45 Aljunied Street 85, #11-31"), new Nric("000-00-0003"), + Set.of()) }; } @@ -45,6 +52,12 @@ public static ReadOnlyAddressBook getSampleAddressBook() { for (Person samplePerson : getSamplePersons()) { sampleAb.addPerson(samplePerson); } + for (Room sampleRoom : getSampleRooms()) { + sampleAb.addRoom(sampleRoom); + } + for (Residency sampleResidency : getSampleResidency()) { + sampleAb.record(sampleResidency); + } return sampleAb; } @@ -57,4 +70,31 @@ public static Set getTagSet(String... strings) { .collect(Collectors.toSet()); } + public static Room[] getSampleRooms() { + return new Room[] { + new Room(new RoomNumber("001"), getTagSet("normal")), + new Room(new RoomNumber("002"), getTagSet("normal")), + new Room(new RoomNumber("003"), getTagSet("normal")), + new Room(new RoomNumber("004"), getTagSet("normal")), + new Room(new RoomNumber("005"), getTagSet("normal")), + }; + } + + public static Residency[] getSampleResidency() { + Person[] guests = getSamplePersons(); + Person[] guestsOne = new Person[] {guests[0], guests[1], guests[2]}; + Person[] guestsTwo = new Person[] {guests[3], guests[4], guests[5]}; + Set guestsOneSet = Arrays.stream(guestsOne).collect(Collectors.toSet()); + Set guestsTwoSet = Arrays.stream(guestsTwo).collect(Collectors.toSet()); + Room[] rooms = getSampleRooms(); + LocalDateTime sampleDateOne = LocalDateTime.of(2021, 11, 1, 12, 0); + LocalDateTime sampleDateTwo = LocalDateTime.of(2021, 11, 2, 12, 0); + LocalDateTime sampleDateThree = LocalDateTime.of(2021, 10, 30, 12, 0); + LocalDateTime sampleDateFour = LocalDateTime.of(2021, 10, 31, 12, 0); + + return new Residency[] { + new Residency(rooms[0], guestsOneSet, sampleDateOne, sampleDateTwo), + new Residency(rooms[1], guestsTwoSet, sampleDateThree, sampleDateFour) + }; + } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index a6321cec2ea..e54bc46e8fe 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -13,6 +13,7 @@ import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Nric; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; @@ -29,18 +30,20 @@ class JsonAdaptedPerson { private final String email; private final String address; private final List tagged = new ArrayList<>(); + private final String nric; /** * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { + @JsonProperty("email") String email, @JsonProperty("address") String address, + @JsonProperty("nric") String nric, @JsonProperty("tagged") List tagged) { this.name = name; this.phone = phone; this.email = email; this.address = address; + this.nric = nric; if (tagged != null) { this.tagged.addAll(tagged); } @@ -57,6 +60,7 @@ public JsonAdaptedPerson(Person source) { tagged.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); + nric = source.getNric().value; } /** @@ -102,8 +106,15 @@ public Person toModelType() throws IllegalValueException { } final Address modelAddress = new Address(address); + if (nric == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Nric.class.getSimpleName())); + } + if (!Nric.isValidNric(nric)) { + throw new IllegalValueException(Nric.MESSAGE_CONSTRAINTS); + } + final Nric modelNric = Nric.of(nric); + final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + return new Person(modelName, modelPhone, modelEmail, modelAddress, modelNric, modelTags); } - } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedResidency.java b/src/main/java/seedu/address/storage/JsonAdaptedResidency.java new file mode 100644 index 00000000000..653f3dda269 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedResidency.java @@ -0,0 +1,111 @@ +package seedu.address.storage; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Nric; +import seedu.address.model.person.Person; +import seedu.address.model.residency.Residency; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomNumber; + +/** + * Jackson-friendly version of {@link Residency}. + */ +class JsonAdaptedResidency { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Residency's %s field is missing!"; + public static final String MESSAGE_DATETIME_CONSTRAINTS_FORMAT = "Residency's %s format is incorrect."; + public static final String MESSAGE_CHECKOUT_BEFORE_CHECKIN = + "Residency's checkout time is before its checkin time!"; + + private final String roomNumber; + private final String[] guestNrics; + private final String checkInTime; + private final String checkOutTime; + + /** + * Constructs a {@code JsonAdaptedResidency} with the given residency details. + */ + @JsonCreator + public JsonAdaptedResidency(@JsonProperty("roomNumber") String roomNumber, + @JsonProperty("guestNRICs") String[] guestNrics, + @JsonProperty("checkInTime") String checkInTime, + @JsonProperty("checkOutTime") String checkOutTime) { + this.roomNumber = roomNumber; + this.guestNrics = guestNrics; + this.checkInTime = checkInTime; + this.checkOutTime = checkOutTime; + } + + /** + * Converts a given {@code Residency} into this class for Jackson use. + */ + public JsonAdaptedResidency(Residency source) { + roomNumber = source.getRoom().getRoomNumber().value; + + List nrics = source.getGuests() + .stream() + .map(person -> person.getNric().value) + .collect(Collectors.toList()); + guestNrics = new String[nrics.size()]; + for (int i = 0; i < nrics.size(); i++) { + guestNrics[i] = nrics.get(i); + } + + checkInTime = source.getCheckInTime().toString(); + checkOutTime = source.getCheckOutTime().map(LocalDateTime::toString).orElseGet(() -> null); + } + + /** + * Converts this Jackson-friendly adapted residency object into the model's {@code Residency} object. + */ + public Residency toModelType(Map nricPersonMap, + Map roomNumberRoomMap) throws IllegalValueException { + // TODO: Check if any data constraints are violated + Set guests = new HashSet<>(); + for (String nric : guestNrics) { + Nric something = Nric.of(nric); + Person x = nricPersonMap.get(something); + guests.add(x); + } + + Room room = roomNumberRoomMap.get(new RoomNumber(roomNumber)); + if (room == null) { + throw new IllegalValueException(RoomNumber.MESSAGE_CONSTRAINTS); + } + + if (checkInTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "check in time")); + } + LocalDateTime checkIn; + try { + checkIn = LocalDateTime.parse(checkInTime); + } catch (DateTimeParseException e) { + throw new IllegalValueException(String.format(MESSAGE_DATETIME_CONSTRAINTS_FORMAT, "check in time")); + } + + // TODO: Check if check out time is before check in time + LocalDateTime checkOut; + try { + checkOut = checkOutTime != null ? LocalDateTime.parse(checkOutTime) : null; + } catch (DateTimeParseException e) { + throw new IllegalValueException(String.format(MESSAGE_DATETIME_CONSTRAINTS_FORMAT, "check out time")); + } + + if (checkOut != null && checkOut.isBefore(checkIn)) { + throw new IllegalValueException(MESSAGE_CHECKOUT_BEFORE_CHECKIN); + } + + return new Residency(room, guests, checkIn, checkOut); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedResidencyBook.java b/src/main/java/seedu/address/storage/JsonAdaptedResidencyBook.java new file mode 100644 index 00000000000..147a21f6153 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedResidencyBook.java @@ -0,0 +1,82 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Nric; +import seedu.address.model.person.Person; +import seedu.address.model.residency.ReadOnlyResidencyBook; +import seedu.address.model.residency.Residency; +import seedu.address.model.residency.ResidencyBook; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomNumber; + +/** + * Jackson-friendly version of {@link ResidencyBook}. + */ +public class JsonAdaptedResidencyBook { + + public static final String MESSAGE_INVALID_CHECKOUT_TIME = "Residency has invalid checkout time"; + + private final List residencies = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedResidencyBook} with the given residencies. + */ + @JsonCreator + public JsonAdaptedResidencyBook(@JsonProperty("residencies") List residencies) { + this.residencies.addAll(residencies); + } + + /** + * Converts a given {@code ResidencyBook} into this class for Jackson use. + */ + public JsonAdaptedResidencyBook(ReadOnlyResidencyBook residencyBook) { + this.residencies.addAll(residencyBook.asUnmodifiableObservableList() + .stream() + .map(JsonAdaptedResidency::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted residency book object into the model's {@code ResidencyBook} object. + */ + public ResidencyBook toModelType(List persons, + List rooms, + boolean allowDuplicates) throws IllegalValueException { + + ResidencyBook residencyBook = new ResidencyBook(allowDuplicates); + + // TODO throw correct error for duplicate residency (check for bugs) + Map nricPersonMap = new HashMap<>(); + Map roomNumberRoomMap = new HashMap<>(); + + for (Person person : persons) { + nricPersonMap.put(person.getNric(), person); + } + + for (Room room : rooms) { + roomNumberRoomMap.put(room.getRoomNumber(), room); + } + + for (JsonAdaptedResidency jsonAdaptedResidency : residencies) { + Residency residency = jsonAdaptedResidency.toModelType(nricPersonMap, roomNumberRoomMap); + // If residency is checked in and this is the records book, + // or is checked out and this is the residency book + if (residency.isCheckedIn() == allowDuplicates) { + throw new IllegalValueException(MESSAGE_INVALID_CHECKOUT_TIME); + } + residencyBook.register(residency); + } + + return residencyBook; + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedRoom.java b/src/main/java/seedu/address/storage/JsonAdaptedRoom.java new file mode 100644 index 00000000000..da4e0a4d5fa --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedRoom.java @@ -0,0 +1,75 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Name; +import seedu.address.model.room.Room; +import seedu.address.model.room.RoomNumber; +import seedu.address.model.room.Vacancy; +import seedu.address.model.tag.Tag; + +/** + * Jackson-friendly version of {@link Room}. + */ +class JsonAdaptedRoom { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + + public final String roomNumber; + private final boolean isVacant; + private final Set tags = new HashSet<>(); + + /** + * Constructs a {@code JsonAdaptedRoom} with the given room details. + */ + @JsonCreator + public JsonAdaptedRoom(@JsonProperty("roomNumber") String roomNumber, @JsonProperty("isVacant") boolean isVacant, + @JsonProperty("tags") Set tags) { + this.roomNumber = roomNumber; + this.isVacant = isVacant; + this.tags.addAll(tags); + } + + /** + * Converts a given {@code Room} into this class for Jackson use. + */ + public JsonAdaptedRoom(Room source) { + roomNumber = source.getRoomNumber().value; + isVacant = source.getVacancy().isVacant(); + tags.addAll(source.getTags().stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted room object into the model's {@code Room} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted room. + */ + public Room toModelType() throws IllegalValueException { + final List roomTags = new ArrayList<>(); + for (JsonAdaptedTag tag : tags) { + roomTags.add(tag.toModelType()); + } + + if (roomNumber == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, RoomNumber.class.getSimpleName())); + } + if (!RoomNumber.isValidRoomNumber(roomNumber)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final RoomNumber modelRoomNumber = new RoomNumber(roomNumber); + final Vacancy modelVacancy = Vacancy.of(isVacant); + final Set modelTags = new HashSet<>(roomTags); + + return new Room(modelRoomNumber, modelVacancy, modelTags); + } +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..423acfc1e2f 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -12,6 +12,9 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.residency.Residency; +import seedu.address.model.residency.ResidencyBook; +import seedu.address.model.room.Room; /** * An Immutable AddressBook that is serializable to JSON format. @@ -21,14 +24,35 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_ROOM = "Rooms list contains duplicate room(s)."; + + public static final String MESSAGE_ROOM_ORDER_ERROR = "Rooms list contains room numbers " + + "which are not in consecutive order."; + private final List persons = new ArrayList<>(); + private final List rooms = new ArrayList<>(); + + private final JsonAdaptedResidencyBook residencyBook; + + + private final JsonAdaptedResidencyBook recordsBook; + + //private final int idCounter; + /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given persons, rooms, residency book and id counter. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { + public JsonSerializableAddressBook(@JsonProperty("persons") List persons, + @JsonProperty("rooms") List rooms, + @JsonProperty("residencyBook") JsonAdaptedResidencyBook residencyBook, + @JsonProperty("recordsBook") JsonAdaptedResidencyBook recordsBook, + @JsonProperty("id counter") int idCounter) { this.persons.addAll(persons); + this.rooms.addAll(rooms); + this.residencyBook = residencyBook; + this.recordsBook = recordsBook; } /** @@ -38,6 +62,9 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2122s1-cs2103t-t13-3.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..c6459b9be05 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -32,6 +32,8 @@ public class MainWindow extends UiPart { // Independent Ui parts residing in this Ui container private PersonListPanel personListPanel; + private RoomListPanel roomListPanel; + private ResidencyListPanel residencyListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -50,6 +52,12 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane roomListPanelPlaceholder; + + @FXML + private StackPane residencyListPanelPlaceholder; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -113,6 +121,12 @@ void fillInnerParts() { personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + roomListPanel = new RoomListPanel(logic.getFilteredRoomList()); + roomListPanelPlaceholder.getChildren().add(roomListPanel.getRoot()); + + residencyListPanel = new ResidencyListPanel(logic.getFilteredRecordList()); + residencyListPanelPlaceholder.getChildren().add(residencyListPanel.getRoot()); + resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -186,6 +200,9 @@ private CommandResult executeCommand(String commandText) throws CommandException handleExit(); } + roomListPanel = new RoomListPanel(logic.getFilteredRoomList()); + roomListPanelPlaceholder.getChildren().add(roomListPanel.getRoot()); + return commandResult; } catch (CommandException | ParseException e) { logger.info("Invalid command: " + commandText); diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 7fc927bc5d9..5619b6a2b12 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -39,6 +39,8 @@ public class PersonCard extends UiPart { @FXML private Label email; @FXML + private Label nric; + @FXML private FlowPane tags; /** @@ -52,6 +54,7 @@ public PersonCard(Person person, int displayedIndex) { phone.setText(person.getPhone().value); address.setText(person.getAddress().value); email.setText(person.getEmail().value); + nric.setText(person.getNric().value); person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); diff --git a/src/main/java/seedu/address/ui/ResidencyCard.java b/src/main/java/seedu/address/ui/ResidencyCard.java new file mode 100644 index 00000000000..8fc4d503401 --- /dev/null +++ b/src/main/java/seedu/address/ui/ResidencyCard.java @@ -0,0 +1,87 @@ +package seedu.address.ui; + +import java.time.format.DateTimeFormatter; +import java.util.Set; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.person.Person; +import seedu.address.model.residency.Residency; + + +public class ResidencyCard extends UiPart { + + private static final String FXML = "ResidencyListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Residency residency; + + @FXML + private HBox cardPane; + @FXML + private Label id; + @FXML + private Label number; + + @FXML + private Label in; + @FXML + private Label out; + @FXML + private FlowPane personInfo; + + + /** + * Creates a {@code ResidencyCode} with the given {@code Residency} and index to display. + */ + public ResidencyCard(Residency residency, int displayedIndex) { + super(FXML); + this.residency = residency; + + id.setText(displayedIndex + ". "); + number.setText(residency.getRoom().getRoomNumber().value); + in.setText(residency.getCheckInTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))); + out.setText(residency.getCheckOutTime().get().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))); + + Set guests = residency.getGuests(); + guests.stream().map(person -> { + String cellInfo = String.format("%s\n%s\n%s\n%s\n%s", + person.getName().toString(), + person.getNric().toString(), + person.getPhone().toString(), + person.getAddress().toString(), + person.getEmail().toString() + ); + return cellInfo; + }).forEach(nricString -> personInfo.getChildren().add(new Label(nricString))); + + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ResidencyCard)) { + return false; + } + + // state check + ResidencyCard card = (ResidencyCard) other; + return id.getText().equals(card.id.getText()) + && residency.equals(card.residency); + } +} diff --git a/src/main/java/seedu/address/ui/ResidencyListPanel.java b/src/main/java/seedu/address/ui/ResidencyListPanel.java new file mode 100644 index 00000000000..3f3c6bb9789 --- /dev/null +++ b/src/main/java/seedu/address/ui/ResidencyListPanel.java @@ -0,0 +1,46 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.residency.Residency; + +public class ResidencyListPanel extends UiPart { + private static final String FXML = "ResidencyListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ResidencyListPanel.class); + + @FXML + private ListView residencyListView; + + /** + * Creates a {@code ResidencyListPanel} with the given {@code ObservableList}. + */ + public ResidencyListPanel(ObservableList residencyList) { + super(FXML); + residencyListView.setItems(residencyList); + residencyListView.setCellFactory(listView -> new ResidencyListPanel.ResidencyListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Residency} using a {@code ResidencyCard}. + */ + class ResidencyListViewCell extends ListCell { + + @Override + protected void updateItem(Residency residency, boolean empty) { + super.updateItem(residency, empty); + + if (empty || residency == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ResidencyCard(residency, 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 00000000000..c1878476070 --- /dev/null +++ b/src/main/java/seedu/address/ui/RoomCard.java @@ -0,0 +1,102 @@ +package seedu.address.ui; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +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.ModelManager; +import seedu.address.model.person.Person; +import seedu.address.model.residency.Residency; +import seedu.address.model.room.Room; + + +/** + * A UI component that displays information of a {@code Room}. + */ +public class RoomCard extends UiPart { + + private static final String FXML = "RoomListCard.fxml"; + + public final Room room; + + /** + * 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 + */ + + @FXML + private HBox cardPane; + + @FXML + private Label number; + + @FXML + private Label vacancy; + + @FXML + private Label id; + + @FXML + private FlowPane tags; + + @FXML + private FlowPane guests; + + /** + * Creates a {@code RoomCard} with the given {@code Room} and index to display. + */ + public RoomCard(Room room, int displayedIndex) { + super(FXML); + this.room = room; + id.setText(displayedIndex + ". "); + number.setText(room.getRoomNumber().value); + vacancy.setText(room.getVacancy().toString()); + room.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + + + Set allGuests = new HashSet<>(); + Optional residency = ModelManager.getInstance().getResidency(room); + if (residency.isPresent()) { + allGuests.addAll(residency.get().getGuests()); + allGuests.stream().map(person -> person.getName().toString()) + .forEach(nameString -> { + guests.getChildren().add(new Label(nameString)); + }); + } + + if (room.getVacancy().toString().equals("Vacant")) { + vacancy.setStyle("-fx-text-fill: #1be3b2 !important;"); + } else { + vacancy.setStyle("-fx-text-fill: #f51d50 !important; -fx-font-style: italic !important;"); + } + } + + @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 id.getText().equals(card.id.getText()) + && room.equals(card.room); + } +} 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 00000000000..f1e84a3a5c0 --- /dev/null +++ b/src/main/java/seedu/address/ui/RoomListPanel.java @@ -0,0 +1,49 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.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; + + /** + * Creates a {@code RoomListPanel} with the given {@code ObservableList}. + */ + public RoomListPanel(ObservableList roomList) { + super(FXML); + roomListView.setItems(roomList); + roomListView.setCellFactory(listView -> new RoomListViewCell()); + } + + /** + * 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, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png index 29810cf1fd9..c6254f677ad 100644 Binary files a/src/main/resources/images/address_book_32.png and b/src/main/resources/images/address_book_32.png differ diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 9ce9bcfb569..313eac9808a 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -307,6 +307,15 @@ -fx-padding: 8 1 8 1; } +.table-header { + -fx-font-family: "Open Sans Regular"; + -fx-font-size: 20px; + -fx-font-weight: 700; + -fx-text-fill: white; + -fx-alignment: center; + -fx-text-alignment: center; +} + #cardPane { -fx-background-color: transparent; -fx-border-width: 0; @@ -350,3 +359,97 @@ -fx-background-radius: 2; -fx-font-size: 11; } + + +/* +==================================== ROOM STYLE ======================================== + */ + + +#guests { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#guests .label { + -fx-text-fill: white; + -fx-background-color: #caa224; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 14; +} + +.cell_occupancy_label { + -fx-font-family: "Open Sans Regular"; + -fx-font-size: 14px; + + /* + -fx-text-fill: #1be3b2 !important; + -fx-background-color: #ac3cb7; + */ + -fx-font-weight: 700 !important; + -fx-background-radius: 10px; + -fx-label-padding: 3px; +} + +.cell_room_sub_label { + -fx-font-family: "Open Sans Regular"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + + +/* +==================================== HISTORY STYLE ======================================== + */ + +#personInfo { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#personInfo .label { + -fx-text-fill: white; + -fx-background-color: #3e7b91; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 14; +} + +.cell_occupancy_label_checkin { + -fx-font-family: "Open Sans Regular"; + -fx-font-size: 14px; + + /* + -fx-text-fill: #1be3b2 !important; + -fx-background-color: #ac3cb7; + */ + -fx-font-weight: 700 !important; + -fx-background-radius: 10px; + -fx-label-padding: 3px; + -fx-text-fill: #d6e31b !important; +} + +.cell_occupancy_label_checkout { + -fx-font-family: "Open Sans Regular"; + -fx-font-size: 14px; + + /* + -fx-text-fill: #1be3b2 !important; + -fx-background-color: #ac3cb7; + */ + -fx-font-weight: 700 !important; + -fx-background-radius: 10px; + -fx-label-padding: 3px; + -fx-text-fill: #f5941d !important; +} + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 32bcf2c8e70..881e7194970 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -1,29 +1,34 @@ - - - + + + - + - @@ -34,25 +39,54 @@ - + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad55..0ab26f85b30 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -27,7 +27,8 @@