diff --git a/README.md b/README.md index 13f5c77403f..97c48bb4f23 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ -[![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-W13-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S1-CS2103T-W13-1/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. +## HR Manager + +HR Manager will help you manage the people to be interviewed, making the scheduling process **easier** and **faster** for your company! + +Tired of losing track of scheduled interviews? +HR manager's interviewee manager’s easy to use features will help you to arrange for upcoming interviews quickly and in your desired manner. + +The data you have provided will also be stored safely and securely for subsequent uses, transferrable to other devices too! + +**_This project is based on the AddressBook-Level3 project created by the SE-EDU initiative._** + diff --git a/build.gradle b/build.gradle index be2d2905dde..944fe88e7dd 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,11 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'HRManager.jar' } defaultTasks 'clean', 'test' + +run { + enableAssertions = true +} diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..ff1e0141a68 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -5,55 +5,68 @@ 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 +### Xu Jiheng - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/JeffZincatz)] [[portfolio](team/jeffzincatz.md)] -* Role: Project Advisor +* Role: Developer +* Responsibilities: + * Position and interview modelling + * List commands (`list_p` and `list_i`) + * Refactoring GUI components + * Formatting for UG and DG + * Reviewing PRs -### Jane Doe +### Chua Sue-Ann - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/sueann-chua)] [[portfolio](team/sueann-chua.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: + * Edit and Assign commands (`edit_p`, `edit_i` and `assign`) + * Proofreading changes to UG and DG -### Johnny Doe +### Mohamed Noriman - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/seaweediman)] [[portfolio](team/seaweediman.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: + * Remark candidate command (`remark_c`) + * Add position command (`add_p`) + * Delete interview command (`delete_i`) + * 'Unassign' command (`unassign`) + * Reviewing PRs -### Jean Doe +### Liu Wanyu - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/wanyu-l)] [[portfolio](team/wanyu-l.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: + * Draft Position Model + * Changes to Storage Component + * Delete position command (`delete_p`) + * Add interview command (`add_i`) + * Code tests -### James Doe +### Nobel Ang - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/angnobel)] [[portfolio](team/angnobel.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: + * Handle Github issues and milestones + * `find_x` command, documentation and tests diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..b1f79467ef2 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,20 +2,42 @@ layout: page title: Developer Guide --- -* Table of Contents + -------------------------------------------------------------------------------------------------------------------- - ## **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 is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). + +-------------------------------------------------------------------------------------------------------------------- +## **Introduction** ## + +HR Manger is a simple to use and easy to learn local desktop application that allows you to easily manage your candidates, positions and interviews. +HR Manager is built on Java and can be run on all major desktop operating systems. HR Manager has a graphic user interface to display information and uses text-based commands to interact with the application. + +-------------------------------------------------------------------------------------------------------------------- +## **Purpose** ## + +The purpose of this guide is to provide a comprehensive documentation of the design and overview of the application for developers to quickly onboard and develop the application. + +You can read the entire guide from the start, which will give you a complete view of the structure of HR Manager. + +Alternatively, you can quickly get started by reading through the [Setting Up](#setting-up-getting-started) and [Design](#design) sections to get a overview of the application. +You can then read the [Feature Implementation](#feature-implementation) for more details of specific features. -------------------------------------------------------------------------------------------------------------------- ## **Setting up, getting started** -Refer to the guide [_Setting up and getting started_](SettingUp.md). + +*HR Manager* is a desktop application built with JavaFX GUI which aims to help with the HR management in small companies. With the application, the user will be able to easily manage their candidates, job postings, and interview sessions. + +To set up the application, please refer to the guide [_Setting up and getting started_](SettingUp.md). + -------------------------------------------------------------------------------------------------------------------- @@ -23,12 +45,12 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
-: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. +:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/AY2122S1-CS2103T-W13-1/tp/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.
### Architecture - +![ArchitectureDiagram](images/ArchitectureDiagram.png) The ***Architecture Diagram*** given above explains the high-level design of the App. @@ -36,7 +58,7 @@ Given below is a quick overview of main components and how they interact with ea **Main components of the architecture** -**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, +**`Main`** has two classes called [`Main`](https://github.com/AY2122S1-CS2103T-W13-1/tp/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2122S1-CS2103T-W13-1/tp/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup methods where necessary. @@ -54,7 +76,7 @@ The rest of the App consists of four components. The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. - +![ArchitectureSequenceDiagram](images/ArchitectureSequenceDiagram.png) Each of the four main components (also shown in the diagram above), @@ -63,19 +85,19 @@ Each of the four main components (also shown in the diagram above), 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. - +![ComponentManagers](images/ComponentManagers.png) The sections below give more details of each component. ### UI component -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +The **API** of this component is specified in [`Ui.java`](https://github.com/AY2122S1-CS2103T-W13-1/tp/tree/master/src/main/java/seedu/address/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/AY2122S1-CS2103T-W13-1/tp/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2122S1-CS2103T-W13-1/tp/tree/master/src/main/resources/view/MainWindow.fxml) The `UI` component, @@ -86,168 +108,191 @@ The `UI` component, ### Logic component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](https://github.com/AY2122S1-CS2103T-W13-1/tp/tree/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: - +![LogicClassDiagram](images/LogicClassDiagram.png) 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. When `Logic` is called upon to execute a command, it uses the `HrManagerParser` class to parse the user command. +1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCandidateCommand`) 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("delete_c 1")` API call. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `delete_c 1` Command](images/DeleteSequenceDiagram.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. +
:information_source: **Note:** The lifeline for `DeleteCandidateCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: - +![ParserClasses](images/ParserClasses.png) How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. +* When called upon to parse a user command, the `HrManagerParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCandidateCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCandidateCommand`) which the `HrManagerParser` returns as a `Command` object. + * Most significantly, the ArgumentTokenizer is used to parse the arguments using the provided prefixes to retrieve the inputs from the user +* All `XYZCommandParser` classes (e.g., `AddCandidateCommandParser`, `DeletePositionCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) - - +**API** : [`Model.java`](https://github.com/AY2122S1-CS2103T-W13-1/tp/tree/master/src/main/java/seedu/address/model/Model.java) +![ModelClassDiagram](images/ModelClassDiagram.png)
+![PersonClassDiagram](images/PersonClassDiagram.png)
+![PositionClassDiagram](images/PositionClassDiagram.png)
+![InterviewClassDiagram](images/InterviewClassDiagram.png)
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 HR Manager data i.e. + * all `Person` objects (which are contained in a `UniquePersonList` object). + * all `Position` objects (which are contained in a `UniquePositionList` object). + * all `Interview` objects (which are contained in a `UniqueInterviewList` 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'. +* stores the currently 'selected' `Position` 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'. +* stores the currently 'selected' `Interview` 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'. +>To be 'observed' means that the UI can be bound to this list so that the UI automatically updates when the data in the list change. * 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 -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](https://github.com/AY2122S1-CS2103T-W13-1/tp/tree/master/src/main/java/seedu/address/storage/Storage.java) - +![StorageClassDiagram](images/StorageClassDiagram.png) The `Storage` component, -* can save both address book data and user preference data in json format, and read them back into corresponding objects. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). -* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) +* can save both HR Manager data and user preference data in json format, and read them back into corresponding objects. +* inherits from both `HrManagerStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). +* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`). ### Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.address.commons` package. -------------------------------------------------------------------------------------------------------------------- -## **Implementation** +## **Documentation, logging, testing, configuration, dev-ops** -This section describes some noteworthy details on how certain features are implemented. +* [Documentation guide](Documentation.md) +* [Testing guide](Testing.md) +* [Logging guide](Logging.md) +* [Configuration guide](Configuration.md) +* [DevOps guide](DevOps.md) -### \[Proposed\] Undo/redo feature +-------------------------------------------------------------------------------------------------------------------- +## **Feature Implementation** ## -#### Proposed Implementation +### **Find Commands** ### -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 `find_c`, `find_p` and `find_i` command allows users to search for candidates, positions and interviews using their parameters. +Generally, they are called `find_x` in this section. -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +The class structure of an execution of `find_x` command is as follows. Only important classes are shown. +![Structure of the find_x command](images/FindClassDiagram.png) -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +The `FindXCommandPredicate` holds the parsed arguments from the command and a `test` method to check if X fufils the condition -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +The sequence diagram of a `find_x` command execution is as follows: -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. +Firstly, the parsing of the command and argument occurs as follows +![Parse_sequence_of_find_x](images/FindParseSequenceDiagram.png) -![UndoRedoState0](images/UndoRedoState0.png) +Continuing the previous diagram, the FindXCommand is executed, and the UI is updated +![Execute_sequence_of_find_x](images/FindExecuteSequenceDiagram.png) -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +#### Design Considerations #### +Aspect: Logical operators and combinations for find fields +* Implemented: AND between separate fields and OR within multiple entries in same field + * eg: `find_c name=alex brad phone=12345678` === `(phone=12345678) AND (name contains alex OR brad)` + * Pros: Easy to implement, simple command format for the most common usecase + * Cons: Unable to search using more complex combination of logical operators +* Alternative: Allow users to specify which operators are used and how they are combined + * Pros: Give granular control to the user for find + * Cons: Very complex command format -![UndoRedoState1](images/UndoRedoState1.png) -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +### **List Commands** ### -![UndoRedoState2](images/UndoRedoState2.png) +The `list_c`, `list_p` and `list_i` command allows users to list all candidates, positions and interviews in the respective display panel. +Generally, they are called `list_x` in this section. -
: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`. +The class structure of an execution of `list_x` command is as follows. Only important classes are shown. -
+![Structure of the list_x command](images/ListClassDiagram.png) -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. +The `ListXCommandPredicate` uses the preset predicate such that all X fulfills the condition. -![UndoRedoState3](images/UndoRedoState3.png) +The sequence diagram of a `list_x` command execution is as follows: -
: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. +Firstly, the parsing of the command and argument occurs as follows -
+![Parse_sequence_of_list_x](images/ListParseSequenceDiagram.png) -The following sequence diagram shows how the undo operation works: +Continuing the previous diagram, the ListXCommand is executed, and the corresponding X panel in the UI is updated -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +![Execute_sequence_of_list_x](images/ListExcecuteSequenceDiagram.png) -
: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. +### **Edit Commands** ### -
- -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The `edit_c`, `edit_p` and `edit_i` commands allow users to edit a specific candidate, position +or interview in the respective display panel. -
: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. - -
+Generally, they are called `edit_x` in this section. -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. +The edit mechanism is facilitated by `editXDescriptor`, where X is the placeholder for the object to be edited, for example +`editPersonDescriptor` for editing candidates, `editPositionDescriptor` for editing positions, and +`editInterviewDescriptor` for editing interviews, and each non-empty field value will replace the corresponding +field value of the object that is being edited. `editXDescriptor` stores the details to edit the candidate, +position or interview with. `editXCommand` extends `Command` and implements the +`Command#execute()` operation, which executes the command and returns a result message to be displayed. -![UndoRedoState4](images/UndoRedoState4.png) +Similar to any other command, the `Command#execute()` operation is exposed in the `Logic` interface as +`Logic#execute()`. Please refer to the 'Logic component' under 'Design' for information on how the `Logic` +component works when a command is executed. -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. +As an example, the parsing and execution of an edit_p command is as follows -![UndoRedoState5](images/UndoRedoState5.png) +![Execute_sequence_of_edit_x](images/EditPositionSequenceDiagram.png) -The following activity diagram summarizes what happens when a user executes a new command: +### **Add commands** ### - +The `add_c`, `add_p` and `add_i` commands allow users to add a candidate, position +or interview in the respective display panel. -#### Design considerations: +Generally, they are called `add_x` in this section. I will also be using X to represent a candidate, position or interview here. -**Aspect: How undo & redo executes:** +The add_x functionality is facilitated by `ModelManager`. It uses the following operation of `ModelManager`. +- `ModelManager#hasX()` — Check if the candidate, position or interview already exists within Hr Manager. If so, `CommandException` will be thrown. -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +- `ModelManager#addX()` — Adds a candidate, position or interview to Hr Manager. If that position already exists, a + `DuplicatePositionException` will be thrown. -* **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. +`AddXCommandParser` and `AddXCommand` are created to achieve this functionality. +![Class_diagram_of_add_x](images/AddPosition/AddPositionClassDiagram.png) -_{more aspects and alternatives to be added}_ +Given below is an example usage scenario and the workflow of the`add_x` command. -### \[Proposed\] Data archiving +Step 1. The user executes command `add_x...`. +`Ui` component reads the command as a string from user's input. After that, `MainWindow` +passes the string to `LogicManager` to manipulate the command. -_{Explain here how the data archiving feature will be implemented}_ +Step 2. `LogicManager` passes the command to `HrManagerParser` to parse the command. Since the command starts +with `add_x`, a new `AddXCommandParser` is created to parse the command further. +Step 3. `AddXCommandParser` uses `ArgumentMultimap` to tokenize the prefixes part the command. After extracting the +information of the object such as the title of a position or the name of a candidate, a new `X` is created and a new +`AddXCommand` is created with that `X`. --------------------------------------------------------------------------------------------------------------------- +Step 4. `AddXCommand` passes the given `X` to `ModelManager#hasX`. If the position does not exist in the app, `AddXCommand` +passes the `Position` to `ModelManager#addPosition()`. -## **Documentation, logging, testing, configuration, dev-ops** +Step 5. `Modelmanager#addX()` updates the respective list with the new added `X`. -* [Documentation guide](Documentation.md) -* [Testing guide](Testing.md) -* [Logging guide](Logging.md) -* [Configuration guide](Configuration.md) -* [DevOps guide](DevOps.md) +To better illustrate this example, the parsing and execution of an add_p command is as follows +![Execute_sequence_of_add_p](images/AddPosition/AddPositionSequence.png) -------------------------------------------------------------------------------------------------------------------- @@ -257,73 +302,259 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts +* is HR professional +* has to manage multiple applicants, roles, and interviews * prefer 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**: help them quickly enter the data into the system, reminders of interview timings and ranking of candidates for roles ### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| 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}* +| Priority | As a …​ | I want to …​ | So that I can…​ | +| -------- | ---------- | ------------------------------ | ---------------------------------------------------------------------- | +| `* * *` | user | be able to add a candidate with all relevant information such as full name, age, contact information, scheduled interview time and date.| | +| `* * *` | user | be able to delete a candidate | remove the application if it was withdrawn entirely | +| `* * *` | user | be able to see all active candidates | | +| `* * *` | user | be able to see the details of a specific candidate | | +| `* * *` | user | be able to edit the information related to a candidate | correct any wrongly filled information | +| `* * *` | user | be able to categorize the candidate, whether it was self-applied or referred | carry out administrative processes afterwards more easily | +| `* * *` | user | be able to add a remark/status to a candidate | make it more visible for the next course of administrative action| +| `* * *` | user | be able to edit comments of a candidate | update any further remarks for them| +| `* * *` | user | be able to delete comments of a candidate | remove mistakenly put remarks entirely | +| `* * *` | user | be able to edit existing tags of a candidate | | +| `* * *` | user | be able to remove existing tags of a candidate | | +| `* * *` | user | be able to search for a particular candidate to see his/her upcoming sessions | | +| `* * *` | user searching for candidates | be able to search for candidates who have a certain remark/description | find candidates by criteria | +| `* * *` | user | be able to add a job position with its job title | | +| `* * *` | user | be able to delete a job position | remove the job posting if it was no longer open | +| `* * *` | user | be able to see all posted job positions | | +| `* * *` | user preparing interviews | be able to delete an interview session for a candidate if it no longer takes place | | +| `* * *` | user preparing interviews | be able to view all candidates who will be interviewed on a particular date | keep track of the interviews planned for that day | +| `* * *` | user after interviews | be able to tag a candidate e.g. by the position they are applying for | group them according to the tags | +| `* *` | user | see all the candidates scheduled for interview sessions for a particular job posting | | +| `* *` | user searching for candidates | be able to filter the candidates for some specific requirements | find the right person for the post more efficiently | +| `* *` | user searching for candidates | be able to search who applied for certain positions | I know all the candidates for that specific position | +| `* *` | user after interviews | be able to mark a candidate as ‘Interviewed’ | | +| `* *` | user preparing interviews | be able to schedule a new interview for a candidate | recruit more talent | +| `* *` | user preparing interviews | be able to delete all other scheduled interview sessions for a particular candidate once I decide to assign him a particular post | | +| `* *` | user preparing interviews | be reminded of what interviews I have the next day | keep track of them | +| `* *` | user preparing interviews | be able to reschedule the interview session for a candidate | so that I can keep up with changes coming from the candidate's end | +| `* *` | user preparing interviews | be able to delete the interview sessions on a particular date | | +| `* *` | user preparing interviews | search what interviews I have for a particular date | better prepare for that day | +| `*` | user preparing interviews | add a co-interviewer to an interview | I know who I will be interviewing the candidate with | +| `*` | user | be able to password lock the application to prevent unauthorised access | | +| `*` | user | encrypt the save file | prevent my data from being easily stolen | ### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +#### Use case: UC01 - Add a candidate + +**MSS** + +1. User requests to add a new candidate, with the initial details of the new candidate. +2. User can see the added candidate. + + Use case ends. + +**Extensions** + +* 1a. The format when adding a candidate is incorrect. + + * 1a1. HR Manager shows an error message. + + Use case ends. + +* 1b. The position title the user provided does not exist in the position list. + + * 1b1. HR Manager shows an error message. + + Use case ends. + -**Use case: Delete a person** +#### Use case: UC02 - List all candidates **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to list all candidates. +2. HR Manager shows a list of all candidates. - Use case ends. + Use case ends. **Extensions** -* 2a. The list is empty. +* 2a. The list of candidates is empty. + * 2a1. HR Manager shows that list is empty. - Use case ends. + Use case ends. + +#### Use case: UC03 - Delete a candidate + +**MSS** + +1. User requests to list all candidates (UC02). +2. User requests to delete a specific candidate. +3. HR Manager deletes the corresponding candidate and displays result. + + Use case ends + +**Extensions** * 3a. The given index is invalid. - * 3a1. AddressBook shows an error message. + * 3a1. HR Manager shows an error message. Use case resumes at step 2. -*{More to be added}* +#### Use case: UC04 - Add a position -### Non-Functional Requirements +**MSS** + +1. User requests to add a new position, with the title of the new job position. +2. User can see the added job position. + + Use case ends. + +**Extensions** + +* 1a. The format when adding a position is incorrect. + + * 1a1. HR Manager shows an error message. + + Use case ends. + +* 1b. The position title the user provided already exists in the position list. -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. + * 1b1. HR Manager shows an error message. -*{More to be added}* + Use case ends. + +#### Use case: UC05 - List all positions + +**MSS** + +1. User requests to list all positions +2. HR Manager shows a list of all positions + + Use case ends. + +**Extensions** + +* 2a. The list of positions is empty. + * 2a1. HR Manager shows that list is empty. + + Use case ends. + +#### Use case: UC06 - Delete a position + +**MSS** + +1. User requests to list all positions (UC05). +2. User requests to delete a specific position. +3. HR Manager deletes the corresponding position and displays result. + + Use case ends + +**Extensions** + +* 2a. The given index is invalid. + + * 2a1. HR Manager shows an error message. + + Use case resumes at step 2. + + +#### Use case: UC07 - Add an interview + +**MSS** + +1. User requests to list all positions (UC05). +2. User requests to list all candidates (UC03). +3. User requests to add an interview with details for the interview. +4. HR Manager adds interview. +5. User can see the added job position. + + Use case ends. + +**Extensions** + +* 3a. The given position is invalid. + + * 3a1. HR Manager shows an error message. + + Use case resumes at step 3. + +* 3b. The given index is invalid. + + * 3b1. HR Manager shows an error message. + + Use case resumes at step 3. + +* 3c. The given date is invalid. + + * 3c1. HR Manager shows an error message. + + Use case resumes at step 3. + +* 3d. The given time is invalid. + + * 3d1. HR Manager shows an error message. + + Use case resumes at step 3. + +* 3e. The given duration is invalid. + + * 3d1. HR Manager shows an error message. + + Use case resumes at step 3. + +* 3f. The given status is invalid. + + * 3d1. HR Manager shows an error message. + + Use case resumes at step 3. + +#### Use case: UC08 - List all interviews + +**MSS** + +1. User requests to list all positions +2. HR Manager shows a list of all positions + + Use case ends. + +#### Use case: UC09 - Delete an interview + +1. User requests to list all interviews (UC08). +2. User requests to delete a specific interview. +3. HR Manager deletes the corresponding position and displays result. + + Use case ends. + +**Extensions** + +* 2a. The given index is invalid. + + * 2a1. HR Manager shows an error message. + + Use case resumes at step 2. + + +### Non-Functional Requirements + +1. Program needs to run on all operating systems with Java 11 installed. +2. Application needs to handle at least 500 candidates and 500 interviews without a noticeable sluggishness in performance for typical usage and no graphical errors. +3. A user with above average typing speed should be able to complete tasks faster using commands than they would have using a click-based interface. ### Glossary * **Mainstream OS**: Windows, Linux, Unix, OS-X * **Private contact detail**: A contact detail that is not meant to be shared with others --------------------------------------------------------------------------------------------------------------------- ## **Appendix: Instructions for manual testing** @@ -334,44 +565,244 @@ testers are expected to do more *exploratory* testing.
-### Launch and shutdown +### Launching the program 1. Initial launch + 1. Download the jar file and copy into an empty folder + 2. Double-click the jar file Expected: Shows the GUI with a set of sample data. The window size may not be optimum. + +1. Saving window preferences + 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 2. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent window size and location is retained. - 1. Download the jar file and copy into an empty folder +### Position Management - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. +#### Add a position -1. Saving window preferences +1. Adding a position + 1. Prerequisite: There must not be a position with `title` 'Software Tester' inside HR Manager. + 2. Test case: `add_p title=Software Tester status=open` +
Expected: Position Software Tester with open status is added to HR Manager. Details of the added position are shown in command feedback box. - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. +2. Adding a position with a title already existing in HR Manager. + 1. Prerequisite: There must be a position with `title` 'Software Tester' inside HR Manager. + 2. Test case: `add_p title=Software Tester` +
Expected: Position is not added. HR Manager states that `Software Tester` already exists in HR Manager. - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. +#### List all positions -1. _{ more test cases …​ }_ +1. Listing all positions + 1. Prerequisite: There must be at least 1 position inside HR Manager. + 2. Test case: `list_p` +
Expected: All positions are listed in HR Manager. -### Deleting a person +#### Delete a position -1. Deleting a person while all persons are being shown +1. Deleting a position + 1. Prerequisite: There must be at least 1 position inside HR Manager. + 2. Test case: `delete_p 1` +
Expected: The first position in the list is deleted from the list. Details of the deleted position shown in the command feedback box. - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +2. Entering an invalid position index + 1. Prerequisite: There must be at least 1 position inside HR Manager + 2. Test case: `delete_p 0` +
Expected: No position is deleted. HR Manager states that the position index must be a non-zero unsigned integer. - 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. +3. Not entering any position index + 1. Prerequisite: There must be at least 1 position inside HR Manager + 2. Test case: `delete_p` +
Expected: No position is deleted. HR Manager states that the command format is invalid. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. +#### Edit a position - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. +1. Editing a position's title + 1. Prerequisite: There must be at least 1 position inside HR Manager. + 2. Test case: `edit_p 1 title=Librarian` +
Expected: The first position in the list has its position edited to 'Librarian'. Details of the edited position are shown in the command feedback box. -1. _{ more test cases …​ }_ +2. Editing a position's title to one that already exists in HR Manager. + 1. Prerequisites: There must be a 'Software Tester' position in HR Manager. The first position's title is not 'Software Tester'. + 2. Test case: `edit_p 1 name=Software Tester` +
Expected: The position is not edited. HR Manager states that the position already exists in HR Manager. -### Saving data +3. Editing a position's status + 1. Prerequisite: There must be at least 1 position inside HR Manager. + 2. Test case: `edit_p 1 status=closed` +
Expected: The first position in the list has its status edited to 'closed'. Details of the edited position are shown in the command feedback box. Any candidate with this position will have their position removed. -1. Dealing with missing/corrupted data files +2. Editing a position's status to an invalid one. + 1. Prerequisite: There must be at least 1 position inside HR Manager. + 2. Test case: `edit_p 1 status=invalid` +
Expected: The position is not edited. HR Manager states that the position status can only be open or closed. + +#### Find positions + +1. Find positions by title + 1. Prerequisite: At least 1 position with the title containing keyword 'Software' inside HR Manager. + 2. Test case: `find_p title=Software` +
Expected: All positions whose title contains 'Software' are listed. + +2. Find positions with a title not in HR Manager + 1. Prerequisite: No position with the title containing the keyword 'Accountant' inside HR Manager. + 2. Test case: `find_p title=Accountant` +
Expected: No position is listed. + +### Candidate Management + +#### Add a candidate + +1. Adding a candidate + 1. Prerequisites : There must not be candidate with `Email` 'johnd@example.com' inside HR Manager. There must be an `Accountant` position in HR Manager. + 2. Test case: `add_c name=John Doe phone=98765432 email=johnd@example.com address=311, Clementi Ave 2, #02-25 position=Accountant` +
Expected: Candidate John Doe is added to HR Manager. Details of the added candidate shown in command feedback box. + +2. Adding a candidate applying for a position that does not exist in HR Manager. + 1. Prerequisites : There must not be candidate with `Email` 'johnd@example.com' inside HR Manager. There must be an `Accountant` position in HR Manager. There must not be a `Bookkeeper` position. + 2. Test case: `add_c name=John Doe phone=98765432 email=johnd@example.com address=311, Clementi Ave 2, #02-25 position=Bookkeeper` +
Expected: Candidate is not added. HR Manager states that `Bookkeeper` does not exist in HR Manager. + +#### List all candidates + +1. Listing all candidates + 1. Prerequisite : There must be at least 1 candidate inside HR Manager. + 2. Test case: `list_c` +
Expected: All candidates are listed in HR Manager. + +#### Delete a candidate + +1. Deleting a candidate + 1. Prerequisite: There must at least 1 candidate inside HR Manager. + 2. Test case: `delete_c 1` +
Expected: First candidate in the list is deleted from the list. Details of the deleted candidate shown in the command feedback box. + +2. Entering an invalid candidate index + 1. Prerequisite: There must at least 1 candidate inside HR Manager. + 2. Test case: `delete_c 0` +
Expected: No candidate is deleted. HR Manager states that the candidate index must be a non-zero unsigned integer. + +3. Not entering any candidate index + 1. Prerequisite: There must be at least 1 candidate inside HR Manager. + 2. Test case: `delete_c` +
Expected: No candidate is deleted. HR Manager states that the command format is invalid. + +#### Remark a candidate + +1. Remarking a candidate + 1. Prerequisite: There must at least 1 candidate inside HR Manager. + 2. Test case: `remark_c 1 remark=Great past experiences" +
Expected: First candidate in the list is given the remark and the remark is displayed together with the candidate's other details. Details of the remarked candidate shown in command feedback box. + +2. Entering an invalid candidate index + 1. Prerequisite: There must at least 1 candidate inside HR Manager + 2. Test case: `remark_c 0 remark=Great past experiences" +
Expected: No candidate is remarked. HR Manager states that the candidate index must be a non-zero unsigned integer. + +#### Edit a candidate + +1. Editing a candidate + 1. Prerequisite: There must be at least 1 candidate inside HR Manager. + 2. Test case : `edit_c 1 name=Aiken Lee` +
Expected: First candidate in the list has their name edited to Aiken Lee. Details of the editedd candidate shown in the command feedback box. + +2. Editing a candidate's position to a position that does not exist in HR Manager. + 1. Prerequisite: There must not be a `Bookkeeper` position in HR Manager. + 2. Test case : `edit_c 1 position=Bookkeeper` +
Expected: Candidate is not edited. HR Manager states that `Bookkeeper` does not exist in HR Manager. + +#### Find candidates + +1. Finding candidates + 1. Prerequisite: There must be an `Accountant` position in HR Manager. There must be at least 1 candidate inside HR Manager that applied for the `Accountant` position. + 2. Test case : `find_c position=Accountant` +
Expected: Candidates that applied for the `Accountant` position are listed in the candidates list. + +2. Finding candidates with a position not in HR Manager + 1. Prerequisite: There must be no `Bookkeeper` position in HR Manager. + 2. Test case: `find_c position=Bookkeeper` +
Expected: No candidates listed. + +### Interview Management + +#### Add an Interview + +1. Adding an interview with multiple candidates to HR Manager + 1. Prerequisites: + * Position `Bookkeeper` exists in HR Manager. + * No other interview for `Bookkeeper` with the same date, time and duration that already exist in HR Manager. + * Current displayed list of candidates has at least 2 entries and the first and second candidates have applied for `Bookkeeper`. + 2. Test case: `add_i position=Bookkeeper c=1 2 date=17/10/2021 time=1400 duration=120 interviewed=pending` +
Expected: new interview for `Bookkeeper` on `17 Oct 2021`, `1400 ~ 1600` with status `PENDING`, with first and second candidates from the current displayed list of candidates is added to the bottom of the list of interviews. + +2. Adding an interview with an invalid date provided + 1. Prerequisites: There must be an `Bookkeeper` position in HR Manager. There must be at least 1 candidate inside HR Manager. First candidate must have applied for `Bookkeeper`. + 2. Test case: `add_i position=Bookkeeper c=1 date=32/10/2021 time=1400 duration=120 interviewed=pending` +
Expected: no interview added, invalid date error message shown in feedback box. + +#### List All Interviews + +1. Listing all interviews in HR Manager + 1. Prerequisite : There must be at least 1 interview inside HR Manager + 2. Test case: `list_i` +
Expected: If filtered list of interviews was displayed previously, the complete list of interviews is rendered. Otherwise, the interview list is re-rendered. This command should not fail if typed correctly. + +#### Delete An Interview + +1. Deleting an interview from HR Manager + 1. Prerequisites: Current displayed list of interviews is not empty + 2. Test case: `delete_i 1` +
Expected: First interview from the current displayed list of interviews is deleted from HR Manager. + +2. Deleting an interview with an invalid index + 1. Prerequisites: Current displayed list of interviews is not empty + 2. Test case: `delete_i 0` +
Expected: No interview deleted, error message and sample input shown in feedback box. + +#### Edit An Interview + +1. Editing an interview in HR Manager + 1. Prerequisites: Current displayed list of interviews is not empty + 2. Test case: `edit_i 1 date=11/11/2021` +
Expected: First interview from the current displayed list of interviews has its date set to 11th November 2021. + +2. Editing an interview in HR Manager with an invalid index + 1. Prerequisites: There must be at least 1 interview in HR Manager + 2. Test case: `edit_i 0 date=12/11/2021` +
Expected: No interview edited, error message and sample input shown in feedback box. + +#### Assign An Interview + +1. Assigning multiple candidates to an interview in HR Manager. + 1. Prerequisites: Current displayed lists of interviews and candidates are not empty and the first two candidates have applied for the position that the first interview is for + 2. Test case: `assign c=1 2 i=1` +
Expected: First and second candidates is assigned to the first interview + +2. Assigning invalid candidates from an interview in HR Manager + 1. Prerequisites: There must be at least 1 candidate and 1 interview in HR Manager + 2. Test case: `assign c=0 2 i=1` +
Expected: No candidate is assigned to the first interview, invalid index error message shown in feedback box + +#### Unassign Candidates + +1. Unassigning multiple candidates from an interview in HR Manager + 1. Prerequisites: Current displayed lists of interviews and candidates are not empty and the first two candidates have been assigned to the first interview + 2. Test case: `unassign c=1 2 i=1` +
Expected: First and second candidates is unassigned from the first interview. + +2. Unassigning invalid candidates from an interview in HR Manager + 1. Prerequisites: There must be at least 1 candidate and 1 interview in HR Manager + 2. Test case: `unassign c=0 1 i=1` +
Expected: No candidates unassigned from the first interview, invalid index error message shown in feedback box. + +#### Find An Interview + +1. Finding an interview with date information + 1. Prerequisites: There must be no interviews on 11th November 2021 + 2. Test case: `find_i date=11/11/2021` +
Expected: No interviews listed + +2. Finding an interview with an invalid time + 1. Test case: `find_i time=2500` +
Expected: Command does not execute, invalid time error message is shown in feedback box - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ -1. _{ more test cases …​ }_ diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..e938e8a2cf3 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,190 +3,687 @@ 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. - -* Table of Contents + + +## Introduction +HR Manger is a simple to use and easy to learn local desktop application that allows you to easily manage your candidates, positions and interviews. +HR Manager is built on Java and can be run on all major desktop operating systems. HR Manager has a graphic user interface to display information and uses text-based commands to interact with the application. + +HR Manager will help you manage the candidates to be interviewed, +making the scheduling process easier and faster for your company!
+It is easy to interact with the app through simple to learn and easy to share text commands!
+Tired of losing track of scheduled interviews?
+HR manager's easy to use features will help you to arrange for upcoming interviews quickly in your desired manner.
+The data you provide will also be stored safely and securely for subsequent uses, +transferable to other devices too! + +-------------------------------------------------------------------------------------------------------------------- + +## Purpose +The purpose of this user guide is to give a complete documentation of HR Manger and allow you to easily get started on HR Manager. +The user guide is also the easiest way for you to troubleshoot any issues and clarify any questions you have with HR Manager.
+Lets simplify your HR processes! + +-------------------------------------------------------------------------------------------------------------------- + +## How to use this user guide? +This user guide is a complete guide for all commands and features for HR Manager. +You can read the whole guide for a comprehensive understanding of the application. +

+Alternatively, you can quickly get started using the [Quick Start](#quick-start) and [Command Summary](#command-summary). +If you face any issues, you can check the details for each command under the [Features](#features) section. +There are 4 sections under the Features section: [General Commands](#feature-general-commands), [Job Position Management](#feature-job-position-management), [Candidate Management](#feature-candidate-management) and [Interview Management](#feature-interview-management). +Each section has its own input table that details the requirements and restrictions of each input of that section. + +-------------------------------------------------------------------------------------------------------------------- + +## HR Manager User Interface +Before going into the features, you may want to familiarise yourself with the user interface. +HR Manager has a simple user interface that consists of 6 main components, which are shown in the image below. +
+ +![UserInterface](images/UserInterface.png) + +#### Table of components of HR Manager's user interface, with reference to the image above + +| No. | Component | Description | +| -------- | ------------------ | ------------------ | +| **1** | Tool Bar | Provides easy access to Exit and Help functions. | +| **2** | Command Input Box | This is where you would type in the commands in HR Manager. | +| **3** | Command Feedback Box | This is where feedback will be displayed after HR Manager receives your command input. | +| **4** | Candidate List | This is where all the candidates stored in HR Manager will be displayed. The list can be filtered by using the relevant commands. | +| **5** | Position List | This is where all the job positions stored in HR Manager will be displayed. The list can be filtered by using the relevant commands. | +| **6** | Interview List | This is where all the interviews stored in HR Manager will be displayed. The list can be filtered by using the relevant commands. | -------------------------------------------------------------------------------------------------------------------- ## Quick start -1. Ensure you have Java `11` or above installed in your Computer. +1. Ensure you have Java `11` or above installed in your Computer. You can check what version of Java you have by following this guide [here](https://www.java.com/en/download/help/version_manual.html). -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +2. Download the latest `HRManager.jar` from [here](https://github.com/AY2122S1-CS2103T-W13-1/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 your HR Manager. -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 app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
![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.
+5. Type a command in the command box, which is the box that says "Enter command here...", and press Enter to execute it. e.g., typing **`help`** and pressing Enter will open the help window.
Some example commands you can try: - * **`list`** : Lists all contacts. +* **`list_c`** : Lists all candidates. - * **`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_c`**`name=Bryan Seah email=bsah@gmail.com phone=12345678 address=311, Clementi Ave 2, #02-25 position=Project Manager` : Adds a contact named `Bryan Seah` to the HR Manager. - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. +* **`delete_c`**`3` : Deletes the 3rd candidate shown in the current candidate list. - * **`clear`** : Deletes all contacts. +* **`clear`** : Deletes all candidates, positions, interviews. - * **`exit`** : Exits the app. +
-1. Refer to the [Features](#features) below for details of each command. +WARNING: Please note that `clear` is an irreversible command and all existing data will be permanently deleted. --------------------------------------------------------------------------------------------------------------------- +
-## Features +* **`exit`** : Exits the app. + +* Refer to [Features](#features) below for details of each command. +-------------------------------------------------------------------------------------------------------------------- +## Features
-**:information_source: Notes about the command format:**
+**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`. +* Words in `` are the parameters to be supplied by the user.
+ e.g., in `add_c name=`, `NAME` is a parameter which can be used as `add_c name=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`. + e.g., `name= [tag=] [status=]` can be used as `name=John Doe tag=friend status=scheduled` or as `name=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. + e.g., `[position=]...​` can be used as ` ` (i.e. 0 times), `position=Accountant`, `position=Accountant position=Bookkeeper` etc. * Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. + e.g., if the command specifies `name= phone=`, `phone= 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, but you specified it multiple times, only the last occurrence of the parameter will be taken.
+ e.g., if you specify `phone=12341234 phone=56785678`, only `phone=56785678` will be taken. * 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`. + e.g., if the command specifies `help 123`, it will be interpreted as `help`.
-### Viewing help : `help` +### Feature: General Commands + +#### Viewing help: `help` -Shows a message explaning how to access the help page. +Shows a message explaining how to access the user guide. ![help message](images/helpMessage.png) -Format: `help` +Format: +`help` -### Adding a person: `add` +#### Clearing all entries : `clear` -Adds a person to the address book. +Clears all entries from HR Manager -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +Format: -
:bulb: **Tip:** -A person can have any number of tags (including 0) -
+`clear` -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` +#### Exiting the program : `exit` -### Listing all persons : `list` +Exits the program. -Shows a list of all persons in the address book. +Format: -Format: `list` +`exit` -### Editing a person : `edit` +### Feature: Job Position Management -Edits an existing person in the address book. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Manage a list of job positions posted by your company, with the simple instructions below! -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. -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. +#### Table of Inputs for Position Management -### Locating persons by name: `find` +| Parameter | Examples | Conditions | +| -------- | ------------------ | ------------------ | +| **TITLE** | `Software engineer`, `Accountant`| Must be alphanumeric (spaces allowed) and contain at least 1 character | +| **INDEX** | `1`, `2`| Must be a positive integer smaller than 2147483647 corresponding to the index of the intended position in the currently displayed list of candidates| +| **STATUS** | `open`, `closed` | Must only be either of the 2 examples for the status of an interview, case insensitive | -Finds persons whose names contain any of the given keywords. +All commands below are subjected to these restrictions except `find_p` -Format: `find KEYWORD [MORE_KEYWORDS]` +> Refer to the [Notes about the command format](#features) for the details on the command format -* The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). - e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +#### Add a position: `add_p` -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +*Adds a job position to the list of positions.* -### Deleting a person : `delete` +Format: -Deletes the specified person from the address book. +`add_p title=` -Format: `delete INDEX` +<u>Example:</u> -* 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, …​ +`add_p title=Assistant` -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. +* Adds a job position with the title of Assistant, with a default status of 'open'. -### Clearing all entries : `clear` +* Adding a job position always sets the position status to 'open'. Refer to `edit_p` on how you can change the status + of a job position to 'closed'. + <br> + <br> -Clears all entries from the address book. +#### <u>List all positions:</u> `list_p` -Format: `clear` +*Displays a list of all the positions stored in the application.* -### Exiting the program : `exit` +<u>Format:</u> -Exits the program. +`list_p` + + <br> + +#### <u>Delete a position:</u> `delete_p` + +*Deletes a position from the list of positions.* + +<u>Format:</u> + +`delete_p <INDEX>` + +<u>Example:</u> + +`delete_p 3` + +* Deletes the 3rd position from the list of positions. +* Also deletes this position from every candidate who applied for the position + <br> + <br> + +#### <u>Edit a position:</u> `edit_p` + +*Edits a specific position's details. Only one edit field is needed, but users cannot edit both fields. + For instance, you can either choose to edit the title of a position, or the status of the position, + but not both at the same time.* + +<u>Format:</u> + +`edit_p <INDEX> [title=<TITLE>]` + +OR<br> + +`edit_p <INDEX> [status=<STATUS>]` + +<u>Example:</u> + +`edit_p 2 title=Data Analyst` + +* Edits the title of the 2nd position in the list to 'Data Analyst'. + +* Editing the title of the specified position will update the position title in any scheduled interviews for + that position, along with any candidates who have applied for that position. + <br> + <br> + +`edit_p 3 status=closed` + +* Edits the status of the 3rd position in the list to closed. +* Setting position status to close will delete the position from every candidate who applied for the position. + <br> + <br> + +#### <u>Find a position:</u> `find_p` + +*Filters the position list based on the parameters provided. Minimum of 1 field is needed. Searching is case-insensitive* + +All fields are not subjected to the restriction in the input table and can take any string. + +<u>Format:</u> + +`find_p [title=<TITLE>]... [status=<STATUS>]...` + +<u>Example:</u> + +`find_p status=closed title=Accountant Engineer` + +* Finds all positions that are closed and title contains "Accountant" or "Engineer" + * (status contains word "closed") AND (title contains word "Accountant" OR "Engineer") +* Within 1 field, keywords are separated by a space + * Command will find jobs that contains at least 1 of the keywords (OR) +* Across different fields + * Command will return jobs that contain all the fields (AND) + + <br> + <br> + +### Feature: Candidate Management + +Manage a list of candidates for your company, with the simple instructions below! +Each candidate is uniquely identified by their email. +Different candidates can have the same name as along as they do not share the same email. + +#### Table of Inputs for Candidate Management + +| Parameter | Examples | Conditions | +| -------- | ------------------ | ------------------ | +| **NAME** | `Alex`, `Clarice`| Any string. Any leading or trailing spaces will be removed | +| **EMAIL** | `alex@gmail.com`, `clarice@usc`| In the format local-part@domain <br> 1. The local-part should only contain alphanumeric characters and these special characters, excluding the parentheses, (+_.-). The local-part may not start or end with any special characters. <br><br>2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels separated by periods. <br> The domain name must: <br> - end with a domain label at least 2 characters long <br> - have each domain label start and end with alphanumeric characters <br> - have each domain label consist of alphanumeric characters, separated only by hyphens| +| **PHONE_NUMBER** | `928389492, 623792` | At least 3 numbers | +| **ADDRESS** | `Block 123, Woodlands Avenue 1, #09-34` | Any string. Any leading or trailing spaces will be removed | +| **POSITION** | `Software engineer`, `teSTer` | Title of any position that is currently open, case insensitive | +| **STATUS** | `None`, `Applied`, `Scheduled`, `Interviewed`, `Accepted`, `Rejected`, `Withdrawn` | Must be one of the given examples, case insensitive | +| **TAG** | `experienced`, `priority` | Must be alphanumeric, without spaces | +| **INDEX** | `1`,`2` | Must be a positive integer smaller than 2147483647 corresponding to the index of the intended candidate in the <U>currently displayed list of candidate<U> | +| **REMARK** | `Great Experience`, `@@@ ___ Test Remark` | Any string, leading or trailing spaces are removed| + +> Status for candidates are different than statuses for position and interviews + +All commands in this section are subjected to the restriction in this table except `find_c` + +> Refer to the [Notes about the command format](#features) for the details on the command format + +#### <u>Add a candidate:</u> `add_c` + +*Adds a candidate to the list of candidates.* + +<u>Format:</u> + +`add_c name=<NAME> email=<EMAIL> phone=<PHONE_NUMBER> address=<ADDRESS> position=<POSITION>... [status=<STATUS>] [tag=<TAG>]...` + +<u>Example:</u> + +`add_c name=Bryan Seah email=bsah@gmail.com phone=12345678 address=311, Clementi Ave 2, #02-25 position=Project Manager status=Scheduled` + +* Subjected to all constraints per the Table of Inputs for Candidate Management +* `STATUS` will default to `APPLIED` if field is left empty + <br> + <br> + +#### <u>List all candidates:</u> `list_c` + +*Displays a list of all the candidates stored in the application.* + +<u>Format:</u> + +`list_c` + + <br> + +#### <u>Delete a candidate:</u> `delete_c` + +*Deletes a candidate along with his/her details from the list of candidates.* + +<u>Format:</u> + +`delete_c <INDEX>` + +<u>Example:</u> + +`delete_c 3` + +* Deletes the 3rd candidate along with his/her details from the list of candidates. + * Also deletes this candidate from any interview he/she was scheduled for. + <br> + <br> + + +#### <u>Remark a candidate:</u> `remark_c` + +*Adds a remark to a candidate.* + +<u>Format:</u> + +`remark_c <INDEX> remark=<REMARK>` + +<u>Example:</u> + +`remark_c 1 remark=20 years of experience` + +* Adds a remark to the 1st candidate that he/she has 20 years of experience. +* There is practically no limit to the length of remark you can add + <br> + <br> + +#### <u>Edit a candidate:</u> `edit_c` -Format: `exit` +*Edits a candidate's details. All the details of the candidate can potentially be edited. At least 1 edit field is needed. Fields specified will be <U>replaced</U> with the new value* -### Saving the data +<u>Format:</u> -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +`edit_c <INDEX> [name=<NAME>] [email=<EMAIL>] [phone=<PHONE_NUMBER>] [address=<ADDRESS>] [status=<STATUS>] [tag=<TAG>]... [position=<POSITION>]...` -### Editing the data file +<u>Example:</u> -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. +`edit_c 3 name=Ryan Koh` -<div markdown="span" class="alert alert-warning">: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. +* Edit the name of the 3rd candidate in the list to Ryan Koh. +* If a candidate has an [interview](#feature-interview-management), editing a status to `APPLIED` is not allowed and an error message will be shown. You can choose to change to other status such as `NONE`. + + +#### <u>Find a Candidate:</u> `find_c` + +*Filters the candidate list based on the parameters provided. Minimum of 1 field is needed. Searching is case-insensitive* + +All fields are not subjected to the restriction in the input table and can take any string + +<u>Format:</u> + +`find_c [name=<NAME>]... [email=<EMAIL>]... [phone=<PHONE_NUMBER>]... [address=<ADDRESS>]... [status=<STATUS>]... [tag=<TAG>]... [position=<POSITION>]...` + +<u>Example:</u> + +`find_c name=Alex tag=recommended priority` + +* Finds all candidates that have the word "Alex" in their name and have position that contain the word "Accountant" or "Engineer" + * (name contains "Alex") AND (title contains Accountant OR Engineer) +* Candidates that will be found + * name="Alex Maslow", tags="recommended", ... + * name="alex", tags="priority candidate",... +* Candidates that will not be found + * name="AlexMaslow", tags="recommended", ... (Word "Alex" not in name) + * name="Alex Maslow", tags="" (No tags containing word "recommended" or "priority") +* Within 1 field, keywords are separated by a space + * Command will find candidates that contains at least 1 of the keywords (OR) +* Across different fields + * Command will return candidates that contain all the fields (AND) + + <br> + <br> + + +### Feature: Interview Management + +Manage a list of scheduled interviews, with the simple instructions below! + +#### Table of Inputs for Interview Management + +| Parameter | Examples | Conditions | +| -------- | ------------------ | ------------------ | +| **POSITION** | `Software engineer`, `Accountant`| Must be added to HR Manager and must have been applied by corresponding candidates before it can be used | +| **INDEX** | `1`, `2`| Must be a positive integer smaller than 2147483647 corresponding to the index of the intended candidate in the <U>currently displayed list of candidates<U/>| +| **DATE** | `18/10/2021` for 18th October 2021, `1/9/2021` for 1st September 2021 | Must be in DD/MM/YYYY form and can tolerate single digit for day and month, but year must be 4 digits | +| **TIME** | `0600` for 6 a.m., `1800` for 6 p.m. | Must be in HHMM, following 24-hour format | +| **DURATION** | `120` for 120 minutes, `75` for 75 minutes | Must a positive integer more than 0 and less than 1440, number of minutes in a day| +| **STATUS** | `pending`, `completed` | Must only be either of the 2 examples for the status of an interview, case insensitive | + +All commands in this section are subjected to the restriction in this table except `find_i`. + +> Refer to the [Notes about the command format](#features) for the details on the command format + +#### <u>Add an interview:</u> `add_i` +Use the following command to record the details of an interview session with the candidate(s) for a position! + +*Adds an interview to the list of interviews.* + +<u>Format:</u> + +`add_i position=<POSITION> date=<DATE> time=<TIME> duration=<DURATION> [c=<INDEX>]... [interviewed=STATUS]` + +<u>Example:</u> + +`add_i position=Accountant c=1 2 date=18/10/2021 time=1400 duration=120 interviewed=pending` + + +* Adds an interview for the position of Accountant, for the 1st and 2nd candidates in the candidate list. + The interview is scheduled to be on 18 October 2020, at 2p.m. and has a duration of 120 minutes. The interview's + status is also provided as "pending", meaning that the interview has yet to be completed. + + Click [here](#table-of-inputs-for-interview-management) to see the conditions and examples for possible inputs. + <br> + <br> + +#### <u>List all interviews:</u> `list_i` + +*Displays a list of all the interviews stored in the application.* + +<u>Format:</u> + +`list_i` + + <br> + +#### <u>Delete an interview:</u> `delete_i` + +*Deletes an interview from the list of interviews.* + +<u>Format:</u> + +`delete_i <INDEX>` + +<u>Example:</u> + +`delete_i 3` + +* Deletes the 3rd position from the list of interviews. +* Also deletes this interview from every candidate who were scheduled this interview + <br> + <br> + + +#### <u>Edit an interview:</u> `edit_i` + +Edits a specific interview in the list of interviews. + +<u>Format:</u> + +`edit_i <INDEX> [position=<POSITION>]... [date=<DATE>]... [time=<TIME>]... [duration=<DURATION>]... [interviewed=<STATUS>]...` + +<u>Example:</u> +`edit_i 2 date=18/10/2021 time=1400` +* Edits the second interview in the interview list and updates the date and time of the interview. + +* All input fields should be provided in the correct format. Please refer to notes on interview command format shown + above to see what constitutes a valid input. + +* At least one input field must be edited. For instance, in the above example, two input fields have been edited - + `date=18/10/2021` for the date of the interview and `time=1400` for the time the interview is scheduled for. + +<div markdown="block" class="alert alert-info"> + +**NOTE:** +`edit_i` cannot be used to edit the candidates assigned to the specified interview. + Please refer to `assign` and `unassign` commands below to see how you can add and remove candidates + from a scheduled interview. + +</div> + <br> + <br> + +#### <u>Assign candidates to interview:</u> `assign` + +*Assigns candidates to a specified interview.* + +<u>Format:</u> + +`assign i=<INTERVIEW_INDEX> c=<CANDIDATE_INDEX>...` + +<u>Example:</u> +`assign i=1 c=2 4` + +* You can input any number of candidates but only 1 interview. +* Adds candidates with candidate index 2 and 4 to the first interview. + +<div markdown="block" class="alert alert-info"> + +**WARNING:** +* If the candidate has not applied to the position, attempting to assign the candidate to an interview + for that position will result in an error message displayed. +</div> + <br> + <br> + +#### <u>Unassign candidates from interview:</u> `unassign` + +*Unassigns candidates from a specified interview.* + +<u>Format:</u> + +`unassign i=<INTERVIEW_INDEX> c=<CANDIDATE_INDEX>...` + +<u>Example:</u> + +`unassign i=1 c=2 4` + +* You can input any number of candidates but only 1 interview. +* Removes candidates with candidate index 2 and 4 from the first interview. +* Inputting `c=*` removes all candidates from an interview. + <br> + <br> + +#### <u>Find an Interview:</u> `find_i` + +*Filters the candidate list based on the parameters provided. Minimum of 1 field is needed. Searching is case-insensitive* + +All fields are not subjected to the restriction in the input table and can take any string. + +<u>Format:</u> + +`find_i [position=<POSITION>]... [c=<CANDIDATE_NAME>]... [date=<DATE>]... [time=<TIME>]... + [duration=<DURATION>]... [interviewed=<STATUS>]...` + +<u>Example:</u> + + find_i date=21/09/2021 time=1600 + +* Finds all interviews that are on 21/09/2021 and occur on 1600 + +<div markdown="block" class="alert alert-info"> + +**NOTE:** + +* Interviews that will be found + * date="21/09/2021", time="1500-1700" + * date="21/09/2021", time="1600-1650" +* Interviews that will not be found + * date="21/09/2020", time="1500-1700", ... (Not on 21/09/2021) + * date="21/09/2021", time="1200-1300" (Interview not occurring on 1600) +* Within 1 field, keywords are separated by a space (except time) + * Command will find candidates that contains at least 1 of the keywords (OR) +* Across different fields + * Command will return candidates that contain all the fields (AND) </div> + <br> + <br> + +### Feature: Storage + +Save information of all candidates, positions and interviews into a data file locally, on your device itself. + +Modification of any information will be recorded immediately. + +They will be saved in `data` folder in separate files: `/data/candidates.json`, `/data/positions.json`, and `/data/interviews.json`. + +Note that `data` will be in the same folder as HR Manager. -### Archiving data files `[coming in v2.0]` +If any entry from any of the data files is invalid, HR Manager will launch without any data entries. -_Details coming soon ..._ +<div markdown="span" class="alert alert-warning"> + +>:exclamation: **Caution:** <br> +The remaining segment for storage is for advanced users, regarding how the storage component is implemented. + +</div> + +The candidate, position and interview information will be saved using the JSON format below. + +For a candidate, +```json + [{ + "name" : "Charlotte Oliveiro", + "phone" : "93210283", + "email" : "charlotte@example.com", + "address" : "Blk 11 Ang Mo Kio Street 74, #11-04", + "remark" : "", + "tagged" : [ "rejected" ], + "positions" : [{ + "title" : "Assistant", + "positionStatus" : "OPEN" + }] +}] +``` + +For a position, + +```json +[{ + "title" : "HR Manager", + "positionStatus" : "OPEN" +}] +``` + +For an interview, + +```json +[{ + "position" : "HR Manager", + "candidateIDs" : [ "-550871537", "-2024498055" ], + "date" : "18/10/2021", + "startTime" : "1400", + "duration" : "120", + "status" : "PENDING" +}] +``` +*Note that an interview does not save a candidate but its unique ID generated within the application.* + +Certain fields are editable directly without repercussions <U>as long as the format is valid (as shown above)</U>, like **date**, **startTime**, **duration** and **status** in `Interviews.json` +However, the same cannot be said for fields of different files sharing the same information, like **positions** in `Candidates.json` and the entire `Positions.json` file. +Any discrepancy could cause HR Manager to display misrepresented information. + +<div markdown="span" class="alert alert-warning"> + +>:exclamation:**Caution:** <br> +In general, modifying stored data directly is strongly discouraged. + +If your changes to the data file made its format invalid, HR Manager will discard all stored data and start with an empty data file at the next run. +</div> -------------------------------------------------------------------------------------------------------------------- -## FAQ +## FAQs +**Q**: When will my data be saved? <br> +**A**: Your data will be automatically saved after any command. -**Q**: How do I transfer my data to another Computer?<br> -**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. +**Q**: How can I export my data? <br> +**A**: You can copy the save files, `/data/candidates.json`, `/data/positions.json`, and `/data/interviews.json` +and transfer it to another system's 'data' folder. <br> +Or better yet, copy the entire `/data` folder and overwrite the data folder of the system you wish to transfer to. +The transferred save files can then be loaded readily when using this application. -------------------------------------------------------------------------------------------------------------------- ## Command summary -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` <br> e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`<br> e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`<br> e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`<br> e.g., `find James Jake` -**List** | `list` -**Help** | `help` +| Action | Format, Examples | Expected result | +| -------- | ------------------ | ------------------ | +| **Help** | `help` | Opened help window. | +| **Clear** | `clear` | HR Manager has been cleared! | +| **Exit** | `exit` | HR Manager exits | +| **Add position** | `add_p title=<TITLE>` <br><br> e.g., `add_p title=Software engineer` | New position added: <br> [Software engineer] | +| **List all positions** | `list_p` | Listed all positions | +| **Delete position** | `delete_p <INDEX>` <br><br> e.g., `delete_p 3` | Deleted Position: [Bookkeeper] | +| **Edit a position** | `edit_p <INDEX> title=<TITLE>` or `edit_p <INDEX> status=<STATUS>` <br><br> e.g., `edit_p 3 status=closed` | Edited Position: [Bookkeeper] | +| **Find a position** | `find_p [title=<TITLE>]... [status=<STATUS>]...` <br><br> e.g., `find_p title=Accountant Engineer status=closed` | Candidates found +| **Add a candidate** | `add_c name=<NAME> email=<EMAIL> phone=<PHONE_NUMBER> address=<ADDRESS> position=<POSITION>...[status=<STATUS>] [tag=<TAG>]...` <br><br> e.g., `add_c name=Bryan Seah email=bsah@gmail.com phone=12345678 address=311, Clementi Ave 2, #02-25 position=Project Manager status=Scheduled` | New candidate added: Bryan Seah; Phone: 12345678; Email: bsah@gmail.com; Address: 311, Clementi Ave 2, #02-25; Status: SCHEDULED; Positions: [Project Manager] | +| **List all candidates** | `list_c` | Listed all candidates | +| **Delete a candidate** | `delete_c <INDEX>`<br><br> e.g., `delete_c 3` | Deleted Candidate: Bryan Seah; Phone: 12345678; Email: bsah@gmail.com; Address: 311, Clementi Ave 2, #02-25; Status: SCHEDULED; Positions: [Project Manager] | +| **Add remark to a candidate** | `remark_c <INDEX> remark=<REMARK>`<br><br>eg.`remark_c 1 remark=20 years of experience` | Added remark to Person: Bryan Seah; Phone: 12345678; Email: bsah@gmail.com; Address: 311, Clementi Ave 2, #02-25; Status: SCHEDULED; Remark: 20 years of experience; Positions: [Project Manager] | +| **Edit a candidate** | `edit_c <INDEX> [name=<NAME>] [email=<EMAIL>] [phone=<PHONE_NUMBER>] [address=<ADDRESS>] [status=<STATUS>] [tag=<TAG>]... [position=<POSITION>]...` <br> e.g., `edit_c 3 phone=98602125 email=bryanseah@gmail.com` | Edited Candidate: Bryan Seah; Phone: 98602125; Email: bryanseah@gmail.com; Address: 311, Clementi Ave 2, #02-25; Status: SCHEDULED; Positions: [Project Manager] | +| **Find candidates** | `find_c [name=<NAME>]... [email=<EMAIL>]... [phone=<PHONE_NUMBER>]... [address=<ADDRESS>]... [status=<STATUS>]... [tag=<TAG>]... [position=<POSITION>]...` <br> e.g., `find_c name=Alex tag=recommended priority` | Candidates Found +| **Add an interview** | `add_i position=<POSITION> [c=<INDEX>]... date=DATE time=TIME duration=DURATION [interviewed=STATUS]` <br><br> e.g., `add_i position=Accountant c=1 2 date=18/10/2021 time=1400 duration=120 interviewed=pending` | New interview added: [Accountant [Bernice Yu, David Li] 18 Oct 2021 14:00 - 16:00 PENDING] | +| **List all interviews** | `list_i` | Listed all interviews | +| **Delete an interview** | `delete_i <INDEX>`<br><br> e.g., `delete_i 1` | Deleted Interview: [Accountant [Bernice Yu, David Li] 18 Oct 2021 14:00 - 16:00 PENDING] | +| **Edit an interview** | `edit_i <INDEX> [position=POSITION]... [date=DATE]... [time=TIME]... [duration=DURATION]... [interviewed=STATUS]...` <br><br>e.g., `edit_i 2 date=21/10/2021 time=1400` | Edited Interview: [Data Analyst [Jenny Lim, Max Tan] 21 Oct 2021 14:00 - 16:00 PENDING] | +| **Assign candidates** | `assign i=<INTERVIEW_INDEX> c=<CANDIDATE_INDEX>...` <br><br>e.g., `assign i=1 c=4`| Candidates added to interview: [Project Manager 20 Oct 2021 15:00 - 16:00 PENDING]: <br> 1. David Li | +| **Unassign candidates** | `unassign i=<INTERVIEW_INDEX> c=<CANDIDATE_INDEX>...` <br><br>e.g., `unassign i=1 c=4`| Candidates removed from interview: [Project Manager 20 Oct 2021 15:00 - 16:00 PENDING]: <br> 1. David Li | +| **Find interview** | `find_i [position=POSITION]... [c=<CANDIDATE_NAME>]... [date=DATE]... [time=TIME]... [duration=DURATION]... [interviewed=STATUS]...` <br><br> e.g., `find_i date=21/09/2021 time=1600` | Interviews found diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..4fa8e642b06 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "HR Manager" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2122S1-CS2103T-W13-1/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..e50ac82e1e7 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: "HR Manager"; font-size: 32px; } } diff --git a/docs/diagrams/AddPosition.puml b/docs/diagrams/AddPosition.puml new file mode 100644 index 00000000000..214a6f148c0 --- /dev/null +++ b/docs/diagrams/AddPosition.puml @@ -0,0 +1,26 @@ +@startuml +'https://plantuml.com/class-diagram +!include style.puml + +skinparam class { + FontColor white + StereotypeFontColor white + BorderColor transparent + ArrowColor LOGIC_COLOR +} + +class Parser << Interface >> LOGIC_COLOR +class Command LOGIC_COLOR +class AddXCommandParser LOGIC_COLOR +class AddXCommand LOGIC_COLOR + +skinparam shadowing false + +Parser <|.. AddXCommandParser +Command <|-- AddXCommand + +hide methods +hide fields +hide circle + +@enduml diff --git a/docs/diagrams/AddPositionSequence.puml b/docs/diagrams/AddPositionSequence.puml new file mode 100644 index 00000000000..4f3ec4003df --- /dev/null +++ b/docs/diagrams/AddPositionSequence.puml @@ -0,0 +1,70 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":HRManagerParser" as HRManagerParser LOGIC_COLOR +participant ":AddPositionCommandParser" as AddPositionCommandParser LOGIC_COLOR +participant "command:AddPositionCommand" as AddPositionCommand 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("add_p title=Accountant") +activate LogicManager + +LogicManager -> HRManagerParser : parseCommand("add_p title=Accountant") +activate HRManagerParser + +create AddPositionCommandParser +HRManagerParser -> AddPositionCommandParser +activate AddPositionCommandParser + +AddPositionCommandParser --> HRManagerParser +deactivate AddPositionCommandParser + +HRManagerParser -> AddPositionCommandParser : parse("title=Accountant") +activate AddPositionCommandParser + +create AddPositionCommand +AddPositionCommandParser -> AddPositionCommand +activate AddPositionCommand + +AddPositionCommand --> AddPositionCommandParser : command +deactivate AddPositionCommand + +AddPositionCommandParser --> HRManagerParser : command +deactivate AddPositionCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddPositionCommandParser -[hidden]-> HRManagerParser +destroy AddPositionCommandParser + +HRManagerParser --> LogicManager : command +deactivate HRManagerParser + +LogicManager -> AddPositionCommand : execute() +activate AddPositionCommand + +AddPositionCommand -> Model : hasPosition(p) +activate Model +AddPositionCommand -> Model : addPosition(p) +Model --> AddPositionCommand + +deactivate Model + +create CommandResult +AddPositionCommand -> CommandResult +activate CommandResult + +CommandResult --> AddPositionCommand +deactivate CommandResult + +AddPositionCommand --> LogicManager : result +deactivate AddPositionCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..7affb956e46 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -7,10 +7,10 @@ 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 : "delete_c 1" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delete_c 1") activate logic LOGIC_COLOR logic -[LOGIC_COLOR]> model : deletePerson(p) @@ -19,7 +19,13 @@ activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> model : deletePersonFromInterview(p) +activate model MODEL_COLOR + +model -[MODEL_COLOR]-> logic +deactivate model + +logic -[LOGIC_COLOR]> storage : saveHrManager(hrManager) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 5731f9cbaa1..499125d6086 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -4,8 +4,8 @@ skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList +HrManager *-right-> "1" UniquePersonList +HrManager *-right-> "1" UniqueTagList UniqueTagList -[hidden]down- UniquePersonList UniqueTagList -[hidden]down- UniquePersonList diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml index 6a6b23a006f..be904198cae 100644 --- a/docs/diagrams/CommitActivityDiagram.puml +++ b/docs/diagrams/CommitActivityDiagram.puml @@ -5,10 +5,10 @@ start 'Since the beta syntax does not support placing the condition outside the 'diamond we place it as the true branch instead. -if () then ([command commits AddressBook]) +if () then ([command commits HrManager]) :Purge redundant states; - :Save AddressBook to - addressBookStateList; + :Save HrManager to + hrManagerStateList; else ([else]) endif stop diff --git a/docs/diagrams/DeletePositionSequenceDiagram.puml b/docs/diagrams/DeletePositionSequenceDiagram.puml new file mode 100644 index 00000000000..d37ef70f4d7 --- /dev/null +++ b/docs/diagrams/DeletePositionSequenceDiagram.puml @@ -0,0 +1,75 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":HrManagerParser" as HrManagerParser LOGIC_COLOR +participant ":DeletePositionCommandParser" as DeletePositionCommandParser LOGIC_COLOR +participant "d:DeletePositionCommand" as DeletePositionCommand 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("delete_p 1") +activate LogicManager + +LogicManager -> HrManagerParser : parseCommand("delete_p 1") +activate HrManagerParser + +create DeletePositionCommandParser +HrManagerParser -> DeletePositionCommandParser +activate DeletePositionCommandParser + +DeletePositionCommandParser --> HrManagerParser +deactivate DeletePositionCommandParser + +HrManagerParser -> DeletePositionCommandParser : parse("1") +activate DeletePositionCommandParser + +create DeletePositionCommand +DeletePositionCommandParser -> DeletePositionCommand +activate DeletePositionCommand + +DeletePositionCommand --> DeletePositionCommandParser : d +deactivate DeletePositionCommand + +DeletePositionCommandParser --> HrManagerParser : d +deactivate DeletePositionCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeletePositionCommandParser -[hidden]-> HrManagerParser +destroy DeletePositionCommandParser + +HrManagerParser --> LogicManager : d +deactivate HrManagerParser + +LogicManager -> DeletePositionCommand : execute() +activate DeletePositionCommand + +DeletePositionCommand -> Model : deletePositionFromPerson(1) +activate Model + +Model --> DeletePositionCommand +deactivate Model + +DeletePositionCommand -> Model : deletePosition(1) +activate Model + +Model --> DeletePositionCommand +deactivate Model + +create CommandResult +DeletePositionCommand -> CommandResult +activate CommandResult + +CommandResult --> DeletePositionCommand +deactivate CommandResult + +DeletePositionCommand --> LogicManager : result +deactivate DeletePositionCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..fe7a4b711ee 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -3,9 +3,9 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR -participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR +participant ":HrManagerParser" as HrManagerParser LOGIC_COLOR +participant ":DeleteCandidateCommandParser" as DeleteCandidateCommandParser LOGIC_COLOR +participant "d:DeleteCandidateCommand" as DeleteCandidateCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR end box @@ -13,56 +13,62 @@ box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("delete_c 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser +LogicManager -> HrManagerParser : parseCommand("delete_c 1") +activate HrManagerParser -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser +create DeleteCandidateCommandParser +HrManagerParser -> DeleteCandidateCommandParser +activate DeleteCandidateCommandParser -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser +DeleteCandidateCommandParser --> HrManagerParser +deactivate DeleteCandidateCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser +HrManagerParser -> DeleteCandidateCommandParser : parse("1") +activate DeleteCandidateCommandParser -create DeleteCommand -DeleteCommandParser -> DeleteCommand -activate DeleteCommand +create DeleteCandidateCommand +DeleteCandidateCommandParser -> DeleteCandidateCommand +activate DeleteCandidateCommand -DeleteCommand --> DeleteCommandParser : d -deactivate DeleteCommand +DeleteCandidateCommand --> DeleteCandidateCommandParser : d +deactivate DeleteCandidateCommand -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser +DeleteCandidateCommandParser --> HrManagerParser : d +deactivate DeleteCandidateCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser +DeleteCandidateCommandParser -[hidden]-> HrManagerParser +destroy DeleteCandidateCommandParser -AddressBookParser --> LogicManager : d -deactivate AddressBookParser +HrManagerParser --> LogicManager : d +deactivate HrManagerParser -LogicManager -> DeleteCommand : execute() -activate DeleteCommand +LogicManager -> DeleteCandidateCommand : execute() +activate DeleteCandidateCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCandidateCommand -> Model : deletePerson(p) activate Model -Model --> DeleteCommand +Model --> DeleteCandidateCommand +deactivate Model + +DeleteCandidateCommand -> Model : deletePersonFromInterview(p) +activate Model + +Model --> DeleteCandidateCommand deactivate Model create CommandResult -DeleteCommand -> CommandResult +DeleteCandidateCommand -> CommandResult activate CommandResult -CommandResult --> DeleteCommand +CommandResult --> DeleteCandidateCommand deactivate CommandResult -DeleteCommand --> LogicManager : result -deactivate DeleteCommand +DeleteCandidateCommand --> LogicManager : result +deactivate DeleteCandidateCommand [<--LogicManager deactivate LogicManager diff --git a/docs/diagrams/EditPositionSequenceDiagram.puml b/docs/diagrams/EditPositionSequenceDiagram.puml new file mode 100644 index 00000000000..75fec669dec --- /dev/null +++ b/docs/diagrams/EditPositionSequenceDiagram.puml @@ -0,0 +1,78 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":HrManagerParser" as HrManagerParser LOGIC_COLOR +participant ":EditPositionCommandParser" as EditPositionCommandParser LOGIC_COLOR +participant "e:EditPositionCommand" as EditPositionCommand LOGIC_COLOR +participant "d:EditPositionDescriptor" as EditPositionDescriptor 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("edit_p ...") +activate LogicManager + +LogicManager -> HrManagerParser : parseCommand("edit_p ...") +activate HrManagerParser + +create EditPositionCommandParser +HrManagerParser -> EditPositionCommandParser +activate EditPositionCommandParser + +EditPositionCommandParser --> HrManagerParser +deactivate EditPositionCommandParser + +HrManagerParser -> EditPositionCommandParser : parse(args) +activate EditPositionCommandParser + +create EditPositionDescriptor +EditPositionCommandParser -> EditPositionDescriptor +activate EditPositionDescriptor + +EditPositionDescriptor --> EditPositionCommandParser : d +deactivate EditPositionDescriptor + +create EditPositionCommand +EditPositionCommandParser -> EditPositionCommand +activate EditPositionCommand + +EditPositionCommand --> EditPositionCommandParser : e +deactivate EditPositionCommand + +EditPositionCommandParser --> HrManagerParser : e +deactivate EditPositionCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditPositionCommandParser -[hidden]-> HrManagerParser +destroy EditPositionCommandParser + +HrManagerParser --> LogicManager : e +deactivate HrManagerParser + +LogicManager -> EditPositionCommand : execute() +activate EditPositionCommand + +EditPositionCommand -> Model : setPosition(1, d) +activate Model + +EditPositionCommand -> Model : updateFilteredPositionList() +deactivate Model + +create CommandResult +EditPositionCommand -> CommandResult +activate CommandResult + +CommandResult --> EditPositionCommand +deactivate CommandResult + +EditPositionCommand --> LogicManager : result +deactivate EditPositionCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/FindClassDiagram.puml b/docs/diagrams/FindClassDiagram.puml new file mode 100644 index 00000000000..27f87b26063 --- /dev/null +++ b/docs/diagrams/FindClassDiagram.puml @@ -0,0 +1,44 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR_T4 +skinparam classBackgroundColor LOGIC_COLOR +package Logic { + Class HrManagerParser + Class FindXCommand + Class CommandResult + Class "{abstract}\nCommand" as Command + Class LogicManager + Interface Logic <<Interface>> +} + +package Model { + Class FindXCommandPredicate MODEL_COLOR +} + +package UI { + Class MainWindow UI_COLOR +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Logic + +MainWindow ..> CommandResult + +LogicManager .right.|> Logic +LogicManager -right->"1" HrManagerParser +HrManagerParser ..> FindXCommandPredicate : creates > +FindXCommandPredicate ..> FindXCommand : creates > +FindXCommand --> FindXCommandPredicate + +FindXCommand -up-|> Command +LogicManager .left.> Command : executes > + +Logic ..> CommandResult +LogicManager .down.> CommandResult +Command .up.> CommandResult : produces > + +MainWindow --> Logic + + +@enduml diff --git a/docs/diagrams/FindExecuteSequenceDiagram.puml b/docs/diagrams/FindExecuteSequenceDiagram.puml new file mode 100644 index 00000000000..7be4149b03b --- /dev/null +++ b/docs/diagrams/FindExecuteSequenceDiagram.puml @@ -0,0 +1,50 @@ +@startuml +!include style.puml + +box UI UI_COLOR_T1 +participant ":MainWindow" as MainWindow UI_COLOR +participant ":DisplayListPanel" as displayListPanel UI_COLOR +participant "listLabel:Label" as listLabel UI_COLOR +end box + +box Logic LOGIC_COLOR_T1 + +participant ":FindXCommand" as FindXCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +activate MainWindow +MainWindow -> FindXCommand : execute +activate FindXCommand + +FindXCommand -> Model : updateFilteredPersonList +activate Model +Model --> FindXCommand +deactivate Model +FindXCommand --> MainWindow : CommandResult +deactivate FindXCommand +FindXCommand -[hidden]-> MainWindow : CommandResult +destroy FindXCommand + +MainWindow -> MainWindow : handleFindX +activate MainWindow + +create displayListPanel +MainWindow -> displayListPanel +activate displayListPanel +displayListPanel --> MainWindow +deactivate displayListPanel + +MainWindow -> listLabel : setText("X (filtered)") +activate listLabel +listLabel --> MainWindow +deactivate listLabel + +MainWindow --> MainWindow +deactivate MainWindow + +@enduml diff --git a/docs/diagrams/FindParseSequenceDiagram.puml b/docs/diagrams/FindParseSequenceDiagram.puml new file mode 100644 index 00000000000..d69bd71c530 --- /dev/null +++ b/docs/diagrams/FindParseSequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml + +box UI UI_COLOR_T1 +participant ":MainWindow" as MainWindow UI_COLOR +end box + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":HRManagerParser" as HRManagerParser LOGIC_COLOR +participant ":FindXCommandParser" as FindXCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant ":FindXCommand" as FindXCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":FindXPredicate" as FindXPredicate MODEL_COLOR +end box + + + -> MainWindow : executeCommand +MainWindow-> LogicManager : execute(find_x ...) +activate MainWindow + +activate LogicManager + +LogicManager -> HRManagerParser : parseCommand(find_x ...) +activate HRManagerParser + +create FindXCommandParser +HRManagerParser -> FindXCommandParser +activate FindXCommandParser +FindXCommandParser --> HRManagerParser +deactivate FindXCommandParser + +HRManagerParser -> FindXCommandParser : parse(args) +activate FindXCommandParser + +create FindXPredicate +FindXCommandParser -> FindXPredicate +activate FindXPredicate +FindXPredicate --> FindXCommandParser +deactivate FindXPredicate + +loop argument is present + + FindXCommandParser -> ParserUtil : parseKeywords + activate ParserUtil + ParserUtil --> FindXCommandParser + deactivate ParserUtil + + FindXCommandParser -> FindXPredicate : setKeyword + activate FindXPredicate + FindXPredicate --> FindXCommandParser + deactivate FindXPredicate +end + +create FindXCommand +FindXCommandParser -> FindXCommand +activate FindXCommand +FindXCommand --> FindXCommandParser +deactivate FindXCommand + +FindXCommandParser --> HRManagerParser +deactivate FindXCommandParser + +HRManagerParser --> LogicManager +deactivate HRManagerParser + +LogicManager --> MainWindow + +@enduml diff --git a/docs/diagrams/InterviewClassDiagram.puml b/docs/diagrams/InterviewClassDiagram.puml new file mode 100644 index 00000000000..61d6ba8fb03 --- /dev/null +++ b/docs/diagrams/InterviewClassDiagram.puml @@ -0,0 +1,33 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Interview <<Rectangle>>{ + + +Class Interview { + <color:#white> LocalTime startTime + <color:#white> LocalDate date + <color:#white> Duration duration + <color:#white> Set<Integer> candidateIDs +} +Class InterviewStatus<<enumeration>> { + <color:#white>PENDING + <color:#white>COMPLETED +} +Class Person +Class Position +Class Interview + +Interview --> "*" Person +Interview --> "1" Position +Interview --> "1" InterviewStatus + +} + +show Interview fields +show InterviewStatus fields + +@enduml diff --git a/docs/diagrams/ListClassDiagram.puml b/docs/diagrams/ListClassDiagram.puml new file mode 100644 index 00000000000..d34ddfdd756 --- /dev/null +++ b/docs/diagrams/ListClassDiagram.puml @@ -0,0 +1,44 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR_T4 +skinparam classBackgroundColor LOGIC_COLOR +package Logic { + Class HrManagerParser + Class ListXCommand + Class CommandResult + Class "{abstract}\nCommand" as Command + Class LogicManager + Interface Logic <<Interface>> +} + +package Model { + Class ModelManager MODEL_COLOR +} + +package UI { + Class MainWindow UI_COLOR +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Logic + +MainWindow ..> CommandResult + +LogicManager .right.|> Logic +LogicManager -right->"1" HrManagerParser +'HrManagerParser ..> ListXCommandPredicate : creates > + +ListXCommand -right-> ModelManager + +ListXCommand -up-|> Command +LogicManager .left.> Command : executes > + +Logic ..> CommandResult +LogicManager .down.> CommandResult +Command .up.> CommandResult : produces > + +MainWindow --> Logic + + +@enduml diff --git a/docs/diagrams/ListExcecuteSequenceDiagram.puml b/docs/diagrams/ListExcecuteSequenceDiagram.puml new file mode 100644 index 00000000000..ed0a0674503 --- /dev/null +++ b/docs/diagrams/ListExcecuteSequenceDiagram.puml @@ -0,0 +1,29 @@ +@startuml +!include style.puml + +box UI UI_COLOR_T1 +participant ":MainWindow" as MainWindow UI_COLOR +participant "xListPanel:XListPanel" as XListPanel UI_COLOR +participant "xListLabel:Label" as XListLabel UI_COLOR +end box + +activate MainWindow + +MainWindow -> MainWindow : handleListX +activate MainWindow + +create XListPanel +MainWindow -> XListPanel +activate XListPanel +XListPanel --> MainWindow +deactivate XListPanel + +MainWindow -> XListLabel : setText("X") +activate XListLabel +XListLabel --> MainWindow +deactivate XListLabel + +MainWindow --> MainWindow +deactivate MainWindow + +@enduml diff --git a/docs/diagrams/ListParseSequenceDiagram.puml b/docs/diagrams/ListParseSequenceDiagram.puml new file mode 100644 index 00000000000..de3b2a1e8a6 --- /dev/null +++ b/docs/diagrams/ListParseSequenceDiagram.puml @@ -0,0 +1,51 @@ +@startuml +!include style.puml + +box UI UI_COLOR_T1 +participant ":MainWindow" as MainWindow UI_COLOR +end box + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":HRManagerParser" as HRManagerParser LOGIC_COLOR +participant ":ListXCommand" as ListXCommand LOGIC_COLOR + + +end box + +box Model MODEL_COLOR_T1 +participant ":ModelManager" as ModelManager MODEL_COLOR +end box + + + -> MainWindow : executeCommand +MainWindow-> LogicManager : execute(list_x ...) +activate MainWindow + +activate LogicManager + +LogicManager -> HRManagerParser : parseCommand(list_x ...) +activate HRManagerParser + +create ListXCommand +HRManagerParser -> ListXCommand +activate ListXCommand +deactivate ListXCommand +HRManagerParser --> LogicManager : ListXCommand +deactivate HRManagerParser + +LogicManager -> ListXCommand : execute(model) + + +ListXCommand -> ModelManager : updateFilteredXList +activate ModelManager +ModelManager --> ListXCommand +deactivate ModelManager + +ListXCommand --> LogicManager : CommandResult +deactivate ListXCommand + + +LogicManager --> MainWindow + +@enduml diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index 6d14b17b361..9a57f3bde46 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -6,7 +6,7 @@ skinparam classBackgroundColor LOGIC_COLOR package Logic { -Class AddressBookParser +Class HrManagerParser Class XYZCommand Class CommandResult Class "{abstract}\nCommand" as Command @@ -27,8 +27,8 @@ Class HiddenOutside #FFFFFF HiddenOutside ..> Logic LogicManager .right.|> Logic -LogicManager -right->"1" AddressBookParser -AddressBookParser ..> XYZCommand : creates > +LogicManager -right->"1" HrManagerParser +HrManagerParser ..> XYZCommand : creates > XYZCommand -up-|> Command LogicManager .left.> Command : executes > @@ -38,7 +38,7 @@ LogicManager --> Storage Storage --[hidden] Model Command .[hidden]up.> Storage Command .right.> Model -note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc +note right of XYZCommand: XYZCommand = AddCandidateCommand, \nFindPositionCommand, etc. Logic ..> CommandResult LogicManager .down.> CommandResult diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 1122257bd9a..4e44458887a 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -5,11 +5,11 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR Package Model <<Rectangle>>{ -Interface ReadOnlyAddressBook <<Interface>> +Interface ReadOnlyHrManager <<Interface>> Interface ReadOnlyUserPrefs <<Interface>> Interface Model <<Interface>> -Class AddressBook -Class ReadOnlyAddressBook +Class HrManager +Class ReadOnlyHrManager Class Model Class ModelManager Class UserPrefs @@ -17,38 +17,34 @@ Class ReadOnlyUserPrefs Class UniquePersonList +Class UniquePositionList +Class UniqueInterviewList Class Person -Class Address -Class Email -Class Name -Class Phone -Class Tag +Class Position +Class Interview } Class HiddenOutside #FFFFFF HiddenOutside ..> Model -AddressBook .up.|> ReadOnlyAddressBook +HrManager .up.|> ReadOnlyHrManager ModelManager .up.|> Model -Model .right.> ReadOnlyUserPrefs -Model .left.> ReadOnlyAddressBook -ModelManager -left-> "1" AddressBook +Model .left.> ReadOnlyUserPrefs +Model .right.> ReadOnlyHrManager +ModelManager -left-> "1" HrManager ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList +HrManager *--> "1" UniquePositionList +HrManager *--> "1" UniqueInterviewList +HrManager *--> "1" UniquePersonList UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag - -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +UniquePositionList --> "~* all" Position +UniqueInterviewList --> "~* all" Interview +ModelManager -->"~* filtered" Position ModelManager -->"~* filtered" Person +ModelManager -->"~* filtered" Interview @enduml diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml index 6ba585cba01..8c062769a72 100644 --- a/docs/diagrams/ParserClasses.puml +++ b/docs/diagrams/ParserClasses.puml @@ -9,7 +9,7 @@ Class XYZCommand package "Parser classes"{ Interface Parser <<Interface>> -Class AddressBookParser +Class HrManagerParser Class XYZCommandParser Class CliSyntax Class ParserUtil @@ -19,12 +19,12 @@ Class Prefix } Class HiddenOutside #FFFFFF -HiddenOutside ..> AddressBookParser +HiddenOutside ..> HrManagerParser -AddressBookParser .down.> XYZCommandParser: creates > +HrManagerParser .down.> XYZCommandParser: creates > XYZCommandParser ..> XYZCommand : creates > -AddressBookParser ..> Command : returns > +HrManagerParser ..> Command : returns > XYZCommandParser .up.|> Parser XYZCommandParser ..> ArgumentMultimap XYZCommandParser ..> ArgumentTokenizer diff --git a/docs/diagrams/PersonClassDiagram.puml b/docs/diagrams/PersonClassDiagram.puml new file mode 100644 index 00000000000..48f994467e2 --- /dev/null +++ b/docs/diagrams/PersonClassDiagram.puml @@ -0,0 +1,37 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Person <<Rectangle>>{ + + +Class Person +Class Status +Class Address +Class Email +Class Name +Class Phone +Class Tag +Class Position +Class Interview + +} + + +Person *--> "*" Position +Person *--> "*" Interview +Person *--> "1" Name +Person *--> "1" Phone +Person *--> "1" Email +Person *--> "1" Status +Person *--> "1" Address +Person *--> "*" Tag + +Name -[hidden]right-> Phone +Phone -[hidden]right-> Address +Address -[hidden]right-> Email + + +@enduml diff --git a/docs/diagrams/PositionClassDiagram.puml b/docs/diagrams/PositionClassDiagram.puml new file mode 100644 index 00000000000..e3127a66975 --- /dev/null +++ b/docs/diagrams/PositionClassDiagram.puml @@ -0,0 +1,28 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR +skinparam PositionStatus fields MODEL_COLOR + +Package Position <<Rectangle>> { + +Class Position +Class Title + +class PositionStatus<<enumeration>> { + <color:#white>OPEN + <color:#white>CLOSED +} + + +show PositionStatus fields +skinparam PositionStatus MODEL_COLOR + +} + +Position --> "1" Title +Position --> PositionStatus + + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 85ac3ea2dee..d5415508c3e 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -14,12 +14,19 @@ Class JsonUserPrefsStorage Interface Storage <<Interface>> Class StorageManager -package "AddressBook Storage" #F4F6F6{ -Interface AddressBookStorage <<Interface>> -Class JsonAddressBookStorage -Class JsonSerializableAddressBook +package "HrManager Storage" #F4F6F6{ +Interface HrManagerStorage <<Interface>> +Class JsonHrManagerStorage +Class JsonSerializableHrManagerCandidates Class JsonAdaptedPerson Class JsonAdaptedTag +Class JsonSerializableHrManagerPositions +Class JsonAdaptedPosition +Class JsonAdaptedTitle +Class PositionStatus +Class JsonSerializableHrManagerInterviews +Class JsonAdaptedInterview +Class InterviewStatus } } @@ -29,15 +36,88 @@ HiddenOutside ..> Storage StorageManager .up.|> Storage StorageManager -up-> "1" UserPrefsStorage -StorageManager -up-> "1" AddressBookStorage +StorageManager -up-> "1" HrManagerStorage Storage -left-|> UserPrefsStorage -Storage -right-|> AddressBookStorage +Storage -right-|> HrManagerStorage JsonUserPrefsStorage .up.|> UserPrefsStorage -JsonAddressBookStorage .up.|> AddressBookStorage -JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson +JsonHrManagerStorage .up.|> HrManagerStorage + +JsonHrManagerStorage ..> JsonSerializableHrManagerCandidates +JsonSerializableHrManagerCandidates --> "*" JsonAdaptedPerson +JsonAdaptedPerson --> "*" JsonAdaptedPosition JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonHrManagerStorage ..> JsonSerializableHrManagerPositions +JsonSerializableHrManagerPositions --> "*" JsonAdaptedTitle +JsonSerializableHrManagerPositions --> "1" PositionStatus + +JsonHrManagerStorage ..> JsonSerializableHrManagerInterviews + +JsonSerializableHrManagerInterviews --> "*" JsonAdaptedInterview +JsonAdaptedInterview --> "1" InterviewStatus + +@enduml + +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor STORAGE_COLOR +skinparam classBackgroundColor STORAGE_COLOR + +package Storage{ + +package "UserPrefs Storage" #F4F6F6{ +Interface UserPrefsStorage <<Interface>> +Class JsonUserPrefsStorage +} + +Interface Storage <<Interface>> +Class StorageManager + +package "HrManager Storage" #F4F6F6{ +Interface HrManagerStorage <<Interface>> +Class JsonHrManagerStorage +Class JsonSerializableHrManagerCandidates +Class JsonAdaptedPerson +Class JsonAdaptedTag +Class JsonSerializableHrManagerPositions +Class JsonAdaptedPosition +Class JsonAdaptedTitle +Class PositionStatus +Class JsonSerializableHrManagerInterviews +Class JsonAdaptedInterview +Class InterviewStatus +} + +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Storage + +StorageManager .up.|> Storage +StorageManager -up-> "1" UserPrefsStorage +StorageManager -up-> "1" HrManagerStorage + +Storage -left-|> UserPrefsStorage +Storage -right-|> HrManagerStorage + +JsonUserPrefsStorage .up.|> UserPrefsStorage +JsonHrManagerStorage .up.|> HrManagerStorage + +JsonHrManagerStorage ..> JsonSerializableHrManagerCandidates +JsonSerializableHrManagerCandidates --> "*" JsonAdaptedPerson +JsonAdaptedPerson --> "*" JsonAdaptedPosition +JsonAdaptedPerson --> "*" JsonAdaptedTag + +JsonHrManagerStorage ..> JsonSerializableHrManagerPositions +JsonSerializableHrManagerPositions --> "*" JsonAdaptedTitle +JsonSerializableHrManagerPositions --> "1" PositionStatus + +JsonHrManagerStorage ..> JsonSerializableHrManagerInterviews + +JsonSerializableHrManagerInterviews --> "*" JsonAdaptedInterview +JsonAdaptedInterview --> "1" InterviewStatus + @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index ecae4876432..82aed44f4e4 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -12,7 +12,11 @@ Class MainWindow Class HelpWindow Class ResultDisplay Class PersonListPanel +Class PositionListPanel Class PersonCard +Class PositionCard +Class InterviewListPanel +Class InterviewCard Class StatusBarFooter Class CommandBox } @@ -33,21 +37,37 @@ UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" PositionListPanel +MainWindow *-down-> "1" InterviewListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow PersonListPanel -down-> "*" PersonCard +PositionListPanel -down-> "*" PositionCard +InterviewListPanel -down-> "*" InterviewCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart PersonListPanel --|> UiPart +PositionListPanel --|> UiPart +InterviewListPanel --|> UiPart PersonCard --|> UiPart +PositionCard --|> UiPart +InterviewCard --|> UiPart +UiPart -[hidden]- Model +Logic -[hidden]- Model +Logic -[hidden]- Model + + StatusBarFooter --|> UiPart HelpWindow --|> UiPart -PersonCard ..> Model +PersonCard .right.> Model + +PositionCard .down.> Model +InterviewCard .down.> Model UiManager -right-> Logic MainWindow -left-> Logic diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml index 96e30744d24..34885420931 100644 --- a/docs/diagrams/UndoRedoState0.puml +++ b/docs/diagrams/UndoRedoState0.puml @@ -15,6 +15,6 @@ State2 -[hidden]right-> State3 hide State2 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State1 @end diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml index 01fcb9b2b96..0e2c8c72d33 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoRedoState1.puml @@ -16,7 +16,7 @@ State2 -[hidden]right-> State3 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml index bccc230a5d1..42c468e5c44 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoRedoState2.puml @@ -3,7 +3,7 @@ skinparam ClassFontColor #000000 skinparam ClassBorderColor #000000 -title After command "add n/David" +title After command "add name=David" package States <<rectangle>> { class State1 as "__ab0:AddressBook__" @@ -14,7 +14,7 @@ package States <<rectangle>> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State3 @end diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml index ea29c9483e4..50bf43b3f34 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoRedoState3.puml @@ -14,7 +14,7 @@ package States <<rectangle>> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml index 1b784cece80..83cbe4c740c 100644 --- a/docs/diagrams/UndoRedoState4.puml +++ b/docs/diagrams/UndoRedoState4.puml @@ -14,7 +14,7 @@ package States <<rectangle>> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml index 88927be32bc..fc89dd99d2d 100644 --- a/docs/diagrams/UndoRedoState5.puml +++ b/docs/diagrams/UndoRedoState5.puml @@ -14,7 +14,7 @@ package States <<rectangle>> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State3 note right on link: State ab2 deleted. diff --git a/docs/images/AddPosition/AddPositionClassDiagram.png b/docs/images/AddPosition/AddPositionClassDiagram.png new file mode 100644 index 00000000000..45475e23515 Binary files /dev/null and b/docs/images/AddPosition/AddPositionClassDiagram.png differ diff --git a/docs/images/AddPosition/AddPositionSequence.png b/docs/images/AddPosition/AddPositionSequence.png new file mode 100644 index 00000000000..7dc773450be Binary files /dev/null and b/docs/images/AddPosition/AddPositionSequence.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 2f1346869d0..127a70787ef 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 1ec62caa2a5..bcd94777526 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..ba60a8423d6 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/EditPositionSequenceDiagram.png b/docs/images/EditPositionSequenceDiagram.png new file mode 100644 index 00000000000..554eedc8adb Binary files /dev/null and b/docs/images/EditPositionSequenceDiagram.png differ diff --git a/docs/images/FindClassDiagram.png b/docs/images/FindClassDiagram.png new file mode 100644 index 00000000000..57b9e1b4f6e Binary files /dev/null and b/docs/images/FindClassDiagram.png differ diff --git a/docs/images/FindExecuteSequenceDiagram.png b/docs/images/FindExecuteSequenceDiagram.png new file mode 100644 index 00000000000..0405dc78f03 Binary files /dev/null and b/docs/images/FindExecuteSequenceDiagram.png differ diff --git a/docs/images/FindParseSequenceDiagram.png b/docs/images/FindParseSequenceDiagram.png new file mode 100644 index 00000000000..3144b73b754 Binary files /dev/null and b/docs/images/FindParseSequenceDiagram.png differ diff --git a/docs/images/InterviewClassDiagram.png b/docs/images/InterviewClassDiagram.png new file mode 100644 index 00000000000..7dd438affed Binary files /dev/null and b/docs/images/InterviewClassDiagram.png differ diff --git a/docs/images/ListClassDiagram.png b/docs/images/ListClassDiagram.png new file mode 100644 index 00000000000..cbbb740b57d Binary files /dev/null and b/docs/images/ListClassDiagram.png differ diff --git a/docs/images/ListExcecuteSequenceDiagram.png b/docs/images/ListExcecuteSequenceDiagram.png new file mode 100644 index 00000000000..53e0272d1ce Binary files /dev/null and b/docs/images/ListExcecuteSequenceDiagram.png differ diff --git a/docs/images/ListParseSequenceDiagram.png b/docs/images/ListParseSequenceDiagram.png new file mode 100644 index 00000000000..d4da67216d0 Binary files /dev/null and b/docs/images/ListParseSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index c3028aa1cda..073f0cd2e9f 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 39d7aec4b33..2f5ff1f1aef 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png index 58ad22ce16a..36bc11506d0 100644 Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ diff --git a/docs/images/PersonClassDiagram.png b/docs/images/PersonClassDiagram.png new file mode 100644 index 00000000000..538f2ada5a4 Binary files /dev/null and b/docs/images/PersonClassDiagram.png differ diff --git a/docs/images/PositionClassDiagram.png b/docs/images/PositionClassDiagram.png new file mode 100644 index 00000000000..2573bfac0ca Binary files /dev/null and b/docs/images/PositionClassDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 82c66f8f16e..dd8baab5e65 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..e078357f0fa 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..78161ca8e26 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UserInterface.png b/docs/images/UserInterface.png new file mode 100644 index 00000000000..9fe850b9cc4 Binary files /dev/null and b/docs/images/UserInterface.png differ diff --git a/docs/images/angnobel.png b/docs/images/angnobel.png new file mode 100644 index 00000000000..d122de953ae Binary files /dev/null and b/docs/images/angnobel.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..8e82549bba5 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/jeffzincatz.png b/docs/images/jeffzincatz.png new file mode 100644 index 00000000000..498f819715e Binary files /dev/null and b/docs/images/jeffzincatz.png differ diff --git a/docs/images/seaweediman.png b/docs/images/seaweediman.png new file mode 100644 index 00000000000..29011fe2b47 Binary files /dev/null and b/docs/images/seaweediman.png differ diff --git a/docs/images/sueann-chua.png b/docs/images/sueann-chua.png new file mode 100644 index 00000000000..a4b45860d59 Binary files /dev/null and b/docs/images/sueann-chua.png differ diff --git a/docs/images/wanyu-l.png b/docs/images/wanyu-l.png new file mode 100644 index 00000000000..0a31a97eb2f Binary files /dev/null and b/docs/images/wanyu-l.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..5771c4d7ebc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,17 @@ --- layout: page -title: AddressBook Level-3 +title: Hr Manager --- -[![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) +[![CI Status](https://github.com/AY2122S1-CS2103T-W13-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S1-CS2103T-W13-1/tp/actions) +[![codecov](https://codecov.io/gh/AY2122S1-CS2103T-W13-1/tp/branch/master/graph/badge.svg)](https://codecov.io/gh/AY2122S1-CS2103T-W13-1/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). +**HR Manager will help you manage the people to be interviewed, making the scheduling process **easier** and **faster** for your company!** 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 Hr Manager, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing Hr Manager, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/angnobel.md b/docs/team/angnobel.md new file mode 100644 index 00000000000..f0a943a4f19 --- /dev/null +++ b/docs/team/angnobel.md @@ -0,0 +1,59 @@ +--- +layout: page +title: Nobel Ang's Project Portfolio Page +--- + +## Project: HR Manager + +HR Manager is a candidate and interview management application used for helping HR Managers of small organisations +or businesses in managing candidates, job positions and interviews all in one application. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java. + +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=angnobel&tabRepo=AY2122S1-CS2103T-W13-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false) + +#### Enhancement Implemented +* **New Feature: `find_c`** + * What it does: The existing `find` command was changed to `find_c` and users are allowed to search across all fields present in candidate + * Justification: This greatly increases the capabilities of the find command, allowing searches specific to all potential fields. Searches could be done across different fields with "and" combinator + +* **New Feature: `find_p`** + * What it does: Similar to the `find_c` command, searches for positions that fulfills the keyword requirement. + * Justification: This extends the search functionality to position list, which is crucial to manage many positions. + +* **New Feature: `find_i`** + * What it does: Similar to the `find_c` command, searches for interviews that fulfills the keywords supplied for each field. The time field is specially handled to find interviews that occur during the time provided. + * Justification: Allows the user to handle many interviews and search for interviews that are occuring at a specific date/time. + +* **Enhancement: status attribute for candidates** + * Add a Status enum for candidates to tag the candidate with their status in the hiring pipeline + * Modified all exisiting commands (add, edit, find) and the GUI to handle and display the data + +* **New Feature: Filtered label on GUI** + * Added a label about each list to indicate what the list represents. The label updates to indicate that the list is filtered if a `find_x` command is run. + +* **Various Bugfixes** + * Fixed small bugs, the non-exhaustive list below + * Clear command returns Address book has been cleared + * Position status not accepting case-insensitive inputs + +#### Contributions to the UG +* Added initial documentation of storage (for candidates) +* Added purpose of UG and *How to use this user guide section* +* Added *Table of Inputs* for position and interviews +* Added details and examples for `find_c`, `find_p` and `find_i` commands + +#### Contributions to the DG +* Added use case for adding new candidate +* Added introduction and purpose of DG +* Added specific details of the `find_x` commands with class diagram, sequence diagram and design consideration for the logical combination within and across fields + +#### Contributions to team-based tasks +* Facilitated weekly meetings by setting agenda and distribution of work +* Handled the creation and management of issues and milestones +* Filtered and tagged bugs after Mock PE + +#### Review contributions +* Full list of [PR Reviewed](https://github.com/AY2122S1-CS2103T-W13-1/tp/pulls?q=is%3Apr+reviewed-by%3Aangnobel) diff --git a/docs/team/jeffzincatz.md b/docs/team/jeffzincatz.md new file mode 100644 index 00000000000..c992a36e8e4 --- /dev/null +++ b/docs/team/jeffzincatz.md @@ -0,0 +1,66 @@ +--- +layout: page +title: Xu Jiheng's Project Portfolio Page +--- + +## Project: HR Manager + +HR Manager is a candidate and interview management application used for helping HR Managers of small organisations +or businesses in managing candidates, job positions and interviews all in one application. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java. + +Given below are my contributions to the project. + +### Code contributed: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/#breakdown=true&search=jeffzincatz) + +### Enhancement Implemented + +* **New Feature: Add job position and position list into the HR Manager** + * What it does: Implements a model of job positions and position list, containing information including the job position title and posting status of all positions, with relevant test cases. + * Justification: This feature is the foundation to allow user to access and manage all the stored position data in the system later with other related commands. + +* **New Feature: Adds the functionality to list all job positions in the display. `list_p`** + * What it does: Display a full list of all positions stored in the system, read from storage. + * Justification: This feature gives the users a comprehensive overview of all the existing job positions for further management. + +* **New Feature: Adds interviews and interview list into the HR Manager** + * What it does: Implements a model of interviews and interview list, containing information including the job position interviewed, date, time, duration, candidates, and interview status of all interviews, with relevant test cases. + * Justification: This feature is the foundation to allow user to access and manage all the stored interview data in the system with other related commands. + +* **New Feature: Adds the functionality to list all job positions in the display. `list_i`** + * What it does: Display a full list of all interviews stored in the system, read from storage. + * Justification: This feature gives the users a comprehensive overview of all the existing interviews for further management. + +* **Enhancement: Improves the UI to a 3-panel display** + * What it does: Display all candidates, positions, and interviews in 3 panels. + * Justification: Compared to the old implementation of 1-panel display, this enables the users to easier view all lists without replacing the previous ones. + +* **Enhancement: Applies a new colour scheme for HR Manager** + * What it does: Changes the graphical appearance of the HR Manager, including highlighting the panel lists, making HR Manager more distinct from other applications. + * Justification: This makes the HR Manager a more distinct product. + +### Contribution to Documentations + +* User Guide: + * Add documentation for `position` and its storage + * Add documentation for: `list_c`, `add_p`, `delete_p`, `list_p`, `list_i`. + +* Developer Guide: + * Add use cases, including for `list_c`, `delete_c`, `list_p`, `delete_p`, `list_i`, `delete_i`. + * Add user stories, including for `add_p`, `delete_p`, `list_p`. + * Added the implementation of the find features (`list_c`, `list_p` and `list_i`), including UML diagrams for relevant classes and general find command execution. + +### Contributions to team-based tasks + +* Set up team project repo +* Documenting user stories in table format in Developer Guide +* Refactor existing candidate command's format to include a "_c" suffix, after new models (positions and interviews) were added +* Fix various documentation bugs in UG and DG + +### Review contributions +* Reviewing team PRs + * [A full list](https://github.com/AY2122S1-CS2103T-W13-1/tp/pulls?q=is%3Apr+is%3Aclosed+reviewed-by%3Ajeffzincatz) + of PRs reviewed by me. + +* Reporting bugs for other teams + * [A full list](https://github.com/JeffZincatz/ped/issues) of bugs reported diff --git a/docs/team/seaweediman.md b/docs/team/seaweediman.md new file mode 100644 index 00000000000..324525b479d --- /dev/null +++ b/docs/team/seaweediman.md @@ -0,0 +1,63 @@ +--- +layout: page +title: Mohamed Noriman's Project Portfolio Page +--- + +### Project: HR Manager + +HR Manager is a candidate and interview management application used for helping HR Managers of small organisations +or businesses in managing candidates, job positions and interviews all in one application. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. + +Given below are my contributions to the project. + +* Code contributed: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=seaweed&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=seaweediman&tabRepo=AY2122S1-CS2103T-W13-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +* **New Feature**: Added the ability to remark candidates. `remark_c` + * This feature allows users to give remarks to a specific candidate, allowing users to give comments to noteworthy candidates. + +* **New Feature**: Added the ability to add job positions. `add_p` + * This feature allows users to add job positions into HR Manager, allowing them to keep track of positions in a company. + +* **New Feature**: Added the ability to delete interviews. `delete_i` + * This feature allows users to delete interviews from HR Manager, enabling them delete unwanted interviews from HR Manager to reduce clutter. + +* **New Feature**: Added the ability to 'unassign' candidates from a specific interview. `unassign` + * Since `edit_i` can't be used to remove candidates from an interview, this feature was added such that users can remove candidates from a scheduled interview. + +* **Enhancements: Updated commands to allow multiple candidate indexes after a single `c=` prefix input** + * Before, to add candidate indexes to add_i command, you had to do `c=1 c=2`. Now, you can put it under a single prefix input, + `c=1 2`. + * Highlights : This enhancement required the use of implementing a new `parseCandidateIndexes` method to parse multiple candidate indexes at the same time. + * Refer to [Issue #131](https://github.com/AY2122S1-CS2103T-W13-1/tp/issues/131) and [PR #144](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/144) for more details. + +* **Enhancements: Updated commands to automatically update all relevant lists displayed on the GUI at the end of a command's execution.** + * Before, after every command, the lists will not update automatically and only updates when you click on the person, position or interview card. Now, after each command, the lists will be updated automatically. + * Highlights : This required changing the constructor of `CommandResult` to take in an enum `CommandType`. In `MainWindow`, new methods were created to handle updating specific lists for different types of commands. + * Refer to [Issue #219](https://github.com/AY2122S1-CS2103T-W13-1/tp/issues/219) and [PR #232](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/232) for more details. + +* **Enhancements: Updated `add_c` and `edit_c` so that it throws an error when a closed `Position` is inputted.** + * Before, you could use `add_c` to add a candidate who is applying for a closed position in the company. Similarly, you could use `edit_c` to edit a candidate to apply for a closed position in the company. + * Refer to [Issue #79](https://github.com/AY2122S1-CS2103T-W13-1/tp/issues/79) and [PR #97](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/97) for more details. + +* **Enhancements: Updated commands that take in position such that regardless of what the user inputs, the position that is stored in the candidate or interview object has the same naming convention as the one in the position list.** + * Before, `BOOKKEEPER` and `Bookkeeper` did not refer to the same position, making it possible to have both of them in the app at the same time. With this change, they both refer to the same position. + * Refer to [Issue #206](https://github.com/AY2122S1-CS2103T-W13-1/tp/issues/206) and [PR #235](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/235) for more details. + +**Contributions to team-based tasks** +* Integrated Java CI into the team repository +* Separated candidate, position, interview classes into separate packages +* Helped setup github pages for the team repository +* Updated index.md to change all AB3 references to HR Manager references. + +**Review contributions** +* [Full list of bugs and suggestions for other teams](https://github.com/seaweediman/ped/issues/) +* [Full list of PRs reviewed](https://github.com/AY2122S1-CS2103T-W13-1/tp/pulls?q=is%3Apr+is%3Aclosed+reviewed-by%3Aseaweediman) + +**Contributions to the UG** +* Added the documentation for the following features: `remark_c`, `add_p`, `delete_i`, `unassign`. + +**Contributions to the DG** +* Added a section for add features which provides an overview of the implementation of all the add commands. The section also includes a UML diagram for the sequence diagram of the add + position command `add_p`. Refer to [PR #259](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/259) +* Updated class diagram for Model, Logic and UI Component. Refer to [PR #281](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/281) and [PR #301](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/301) for more details. diff --git a/docs/team/sueann-chua.md b/docs/team/sueann-chua.md new file mode 100644 index 00000000000..ac3340d9b5d --- /dev/null +++ b/docs/team/sueann-chua.md @@ -0,0 +1,69 @@ +--- +layout: page +title: Chua Sue-Ann's Project Portfolio Page +--- + +## Project: HR Manager + +HR Manager is a candidate and interview management application used for helping HR Managers of small organisations +or businesses in managing candidates, job positions and interviews all in one application. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java. + +Given below are my contributions to the project. + +#### 1. 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=sueann-chua&tabRepo=AY2122S1-CS2103T-W13-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +#### 2. Enhancements implemented +The edit features were sometimes a challenge to implement due to the cascading effects +on the other objects when one object was edited. For example, editing a position would mean that the candidates applied to the position, and the interviews scheduled for +that position would need to be updated as well. + +* **New feature: Added the functionality to edit positions** `edit_p` + * The feature was added to allow users to edit either the title or status of a position added to the HR Manager app. + +* **New feature: Added the functionality to edit interviews** `edit_i` + * The feature was added to allow users to edit any number of fields of an interview, including the position, date, time, duration and status using the HR Manager app. + +* **New feature: Added the functionality to assign candidates to a specified interview** `assign` + * Since the edit_i feature does not allow for users to edit the candidates assigned to the interview, this feature + was added so users can assign candidates to a scheduled interview. + +* **Enhancement: bugfix for `edit_c` command where editing of candidate's applied position was not reflected in the +list of interviews** + * With this bugfix, `EditCandidateCommand#Execute` now checks if the edited candidate's positions match the + positions of their scheduled interviews and updates the interview list accordingly. Refer to [Issue #194](https://github.com/AY2122S1-CS2103T-W13-1/tp/issues/194) for more details on the + bug. + +* **Enhancement: bugfix for `edit_p` command where users were able to edit both title and status of the position.** + * We decided against this behaviour as we did not want users to use edit_p as an alternative to adding new + positions. Refer to [PR #265](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/265) or + [Issue #169](https://github.com/AY2122S1-CS2103T-W13-1/tp/issues/169) for more details. +* **Enhancement: change edit functionality for `edit_i` command** + * This enhancement restricts the editing of candidates assigned to `assign` and `unassign` commands. + * My contribution to this enhancement was to remove the functionality to edit candidates using the `edit_i` command. Please refer to [Issue ##220](https://github.com/AY2122S1-CS2103T-W13-1/tp/issues/220) or + [PR #254](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/254) for more details. + +* **Enhancement: GUI for position labels for candidate list and status labels for position list.** + * Edited the `DarkTheme.css` to add labels for the positions and position status. Please refer to [PR #78](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/78) for more details. + +#### 3. Contributions to the UG +* Added the documentation for the following features: `add c`, `delete c`, `edit_p`, `edit_i`, `assign` + +#### 4. Contributions to the DG +* Added an extension for use case: Deleting a candidate. + +* Added a section for edit features which provides an overview of the implementation of all the edit features, such as +`edit_c`, `edit_p` and `edit_i`. The section also includes a UML diagram for the sequence diagram of the edit + position command `edit_p`. + +#### 5. Contributions to team-based tasks +* Completed the necessary general code enhancements regarding changing all references of AB3 to HR Manager. + +* Helped make the release for v1.3. + +* Created UI Mock-up for intended final product. + +#### 6. Review contributions +* [Complete list](https://github.com/AY2122S1-CS2103T-W13-1/tp/pulls?q=is%3Apr+is%3Aclosed+reviewed-by%3A%40me) + of PRs reviewed by me. + diff --git a/docs/team/wanyu-l.md b/docs/team/wanyu-l.md new file mode 100644 index 00000000000..3f6c14a04da --- /dev/null +++ b/docs/team/wanyu-l.md @@ -0,0 +1,77 @@ +--- +layout: page +title: Liu Wanyu's Project Portfolio Page +--- + +### Project: HR Manager + +HR Manager is a candidate and interview management application used for handling that enables recruiters of small organisations +to keep track of candidates, job positions and scheduled interviews easily and efficiently. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java. + +Given below are my contributions to the project. + +### Code contributed + +[RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=wanyu&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=wanyu-l&tabRepo=AY2122S1-CS2103T-W13-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +### Enhancements Implemented + +* **Enhanced Feature**: Changed the Storage Component to store data in different files + * Storage Component now reads from and saves to 3 different json files, corresponding to candidates, job positions and interviews. + * File that stores job positions stores their respective title and status, whether it is still open for hiring or closed. + * File that stores candidates also stores title(s) and respective status(es) of job position(s) which they applied to. + * File that stores interviews store hashcode generated from particulars of respective candidates instead of all details of candidates. + +* **New Feature**: Added functionality to delete a job position. `delete_p` + * This feature allows the user to delete a job position from the current list of job positions in HR Manager, once deemed as not required by the user, + perhaps when the company no longer require visibility of an already closed job position after a certain period of time. + +* **New Feature**: Added functionality to add an interview. `add_i` + * This feature allows the user to add details of a scheduled interview session for a particular job position in the company to HR Manager. + +* **Enhanced Feature**: Updated GUI for display of interviews for each candidate. + * Previously, the particulars of candidate occasionally overlap with details of interviews upon certain interactions, causing slight inconvenience. + This feature changed the text colour of each candidate's interview(s) and implementation of the display for better visuals and to prevent overlapping display text. + +* **Enhanced Feature**: Updated `edit_c` to disallow certain status changes of a candidate. + * This feature disallowed user's attempt to edit the status of a candidate to APPLIED when candidate already has interview(s) and was implemented to provide better feedback. + It changed the way `edit_c` interacts with the user by rejecting the input and showing an error message, when previously HR Manager accepts the input but does not change candidate's status to APPLIED. + + +### Contributions to the UG +* Added Introduction +* Added FAQs +* Added Parameter Table for interview related commands +* Added the documentation for feature `add_i` +* Updated the documentation for storage component + + +### Contributions to the DG +* Added use case for adding an interview +* Updated StorageClassDiagram +* Renamed AddressBook to HrManager in the following diagrams: + * ParserClasses diagram + * LogicClass diagram + +### Contributions to team-based tasks +* Renamed Address App to HR Manager as title of application +* Helped teammates in resolving bugs +* Helped in reviewing teammates' PR + +### Review/mentoring contributions +* Non-exhaustive list of some PRs reviewed: + * [PR #43](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/43) + * [PR #64](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/64) + * [PR #67](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/67) + * [PR #78](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/78) + * [PR #234](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/234) + * [PR #235](https://github.com/AY2122S1-CS2103T-W13-1/tp/pull/235) + + +* [Full list of PRs reviewed](https://github.com/AY2122S1-CS2103T-W13-1/tp/pulls?q=is%3Apr+is%3Aclosed+reviewed-by%3Awanyu-l) + + +* Reported bugs and suggestions for other teams in the class + * [Full list of bugs and suggestions](https://github.com/wanyu-l/ped/issues) + diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md index 8919d8eaa17..a573c6a878a 100644 --- a/docs/tutorials/AddRemark.md +++ b/docs/tutorials/AddRemark.md @@ -23,7 +23,7 @@ For now, let’s keep `RemarkCommand` as simple as possible and print some outpu **`RemarkCommand.java`:** ``` java -package seedu.address.logic.commands; +package seedu.address.logic.candidate; import seedu.address.model.Model; diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..4f0669110c7 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -15,15 +15,15 @@ import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; +import seedu.address.model.HrManager; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyHrManager; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; +import seedu.address.storage.HrManagerStorage; +import seedu.address.storage.JsonHrManagerStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; @@ -48,7 +48,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing HR Manager ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -56,8 +56,10 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + HrManagerStorage hrManagerStorage = new JsonHrManagerStorage(userPrefs.getHrManagerCandidatesFilePath(), + userPrefs.getHrManagerPositionsFilePath(), userPrefs.getHrManagerInterviewsFilePath()); + + storage = new StorageManager(hrManagerStorage, userPrefsStorage); initLogging(config); @@ -74,20 +76,20 @@ public void init() throws Exception { * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional<ReadOnlyAddressBook> addressBookOptional; - ReadOnlyAddressBook initialData; + Optional<ReadOnlyHrManager> addressBookOptional; + ReadOnlyHrManager initialData; try { - addressBookOptional = storage.readAddressBook(); + addressBookOptional = storage.readHrManager(); if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + logger.info("Data file not found. Will be starting with a sample HR Manager"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleHrManager); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty HR Manager"); + initialData = new HrManager(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty HR Manager"); + initialData = new HrManager(); } return new ModelManager(initialData, userPrefs); @@ -151,7 +153,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty HR Manager"); initializedPrefs = new UserPrefs(); } @@ -167,13 +169,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting HR Manager " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping HR Manager ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..de0b887f9cd 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_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_INTERVIEW_DISPLAYED_INDEX = "The interview index provided is invalid"; + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The candidate index provided is invalid"; + public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d candidates listed!"; + public static final String MESSAGE_POSITIONS_LISTED_OVERVIEW = "%1$d positions listed!"; + public static final String MESSAGE_INVALID_POSITION_DISPLAYED_INDEX = "The position index provided is invalid"; + public static final String MESSAGE_INTERVIEW_LISTED_OVERVIEW = "%1$d interviews listed!"; } diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java index 19536439c09..e4cdaf12626 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -1,5 +1,7 @@ package seedu.address.commons.core.index; +import java.util.Objects; + /** * Represents a zero-based or one-based index. * @@ -51,4 +53,9 @@ public boolean equals(Object other) { || (other instanceof Index // instanceof handles nulls && zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check } + + @Override + public int hashCode() { + return Objects.hashCode(zeroBasedIndex); + } } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/Command.java similarity index 84% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/seedu/address/logic/Command.java index 64f18992160..b82a9004f05 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/Command.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package seedu.address.logic; -import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.candidate.exceptions.CommandException; import seedu.address.model.Model; /** diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/CommandResult.java similarity index 54% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/seedu/address/logic/CommandResult.java index 92f900b7916..fb2f9bfd2a8 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.address.logic; import static java.util.Objects.requireNonNull; @@ -10,40 +10,38 @@ public class CommandResult { private final String feedbackToUser; - - /** Help information should be shown to the user. */ - private final boolean showHelp; - - /** The application should exit. */ - private final boolean exit; - - /** - * Constructs a {@code CommandResult} with the specified fields. - */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { - this.feedbackToUser = requireNonNull(feedbackToUser); - this.showHelp = showHelp; - this.exit = exit; + private final CommandType commandType; + + public enum CommandType { + HELP, + EXIT, + GENERAL, + LIST_C, + LIST_P, + LIST_I, + FIND_C, + FIND_P, + FIND_I, + CANDIDATE, + POSITION, + INTERVIEW, } /** * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, * and other fields set to their default value. */ - public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + public CommandResult(String feedbackToUser, CommandType commandType) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.commandType = commandType; } public String getFeedbackToUser() { return feedbackToUser; } - public boolean isShowHelp() { - return showHelp; - } - - public boolean isExit() { - return exit; + public CommandType getCommandType() { + return commandType; } @Override @@ -59,13 +57,12 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) - && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && commandType.equals(otherCommandResult.commandType); } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, commandType); } } diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..fa01459c4d7 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,11 +4,12 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.candidate.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.model.interview.Interview; import seedu.address.model.person.Person; +import seedu.address.model.position.Position; /** * API of the Logic component @@ -16,27 +17,56 @@ public interface Logic { /** * Executes the command and returns the result. + * * @param commandText The command as entered by the user. * @return the result of the command execution. * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. + * @throws ParseException If an error occurs during parsing. */ CommandResult execute(String commandText) throws CommandException, ParseException; /** - * Returns the AddressBook. + * Returns the HR Manager. * - * @see seedu.address.model.Model#getAddressBook() + * @see seedu.address.model.Model#getHrManager() */ - ReadOnlyAddressBook getAddressBook(); + ReadOnlyHrManager getHrManager(); - /** Returns an unmodifiable view of the filtered list of persons */ + /** + * Returns an unmodifiable view of the filtered list of persons + */ ObservableList<Person> getFilteredPersonList(); /** - * Returns the user prefs' address book file path. + * Returns an unmodifiable view of the filtered list of positions + */ + ObservableList<Position> getFilteredPositionList(); + + /** + * Returns an unmodifiable view of the filtered list of interviews + */ + ObservableList<Interview> getFilteredInterviewList(); + + /** + * Returns the user prefs' HR Manager candidate file path. + * + * @return Interview file path. + */ + Path getHrManagerCandidatesFilePath(); + + /** + * Returns the user prefs' HR Manager position file path. + * + * @return Interview file path. + */ + Path getHrManagerPositionsFilePath(); + + /** + * Returns the user prefs' HR Manager interview file path. + * + * @return Interview file path. */ - Path getAddressBookFilePath(); + Path getHrManagerInterviewsFilePath(); /** * Returns the user prefs' GUI settings. diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..46fd3b9b90b 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -7,14 +7,14 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.logic.parser.HrManagerParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.model.interview.Interview; import seedu.address.model.person.Person; +import seedu.address.model.position.Position; import seedu.address.storage.Storage; /** @@ -26,7 +26,7 @@ public class LogicManager implements Logic { private final Model model; private final Storage storage; - private final AddressBookParser addressBookParser; + private final HrManagerParser hrManagerParser; /** * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. @@ -34,7 +34,7 @@ public class LogicManager implements Logic { public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + hrManagerParser = new HrManagerParser(); } @Override @@ -42,11 +42,11 @@ public CommandResult execute(String commandText) throws CommandException, ParseE logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); + Command command = hrManagerParser.parseCommand(commandText); commandResult = command.execute(model); try { - storage.saveAddressBook(model.getAddressBook()); + storage.saveHrManager(model.getHrManager()); } catch (IOException ioe) { throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); } @@ -55,8 +55,8 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); + public ReadOnlyHrManager getHrManager() { + return model.getHrManager(); } @Override @@ -65,8 +65,28 @@ public ObservableList<Person> getFilteredPersonList() { } @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); + public ObservableList<Position> getFilteredPositionList() { + return model.getFilteredPositionList(); + } + + @Override + public ObservableList<Interview> getFilteredInterviewList() { + return model.getFilteredInterviewList(); + } + + @Override + public Path getHrManagerCandidatesFilePath() { + return model.getHrManagerCandidatesFilePath(); + } + + @Override + public Path getHrManagerPositionsFilePath() { + return model.getHrManagerPositionsFilePath(); + } + + @Override + public Path getHrManagerInterviewsFilePath() { + return model.getHrManagerInterviewsFilePath(); } @Override diff --git a/src/main/java/seedu/address/logic/candidate/AddCandidateCommand.java b/src/main/java/seedu/address/logic/candidate/AddCandidateCommand.java new file mode 100644 index 00000000000..e777882965e --- /dev/null +++ b/src/main/java/seedu/address/logic/candidate/AddCandidateCommand.java @@ -0,0 +1,97 @@ +package seedu.address.logic.candidate; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.model.position.Position.MESSAGE_POSITION_CLOSED; +import static seedu.address.model.position.Position.MESSAGE_POSITION_DOES_NOT_EXIST; + +import java.util.HashSet; +import java.util.Set; + +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; + +/** + * Adds a candidate to the HR Manager. + */ +public class AddCandidateCommand extends Command { + + public static final String COMMAND_WORD = "add_c"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a candidate to the HR Manager.\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_POSITION + "POSITION... " + + "[" + PREFIX_STATUS + "STATUS] " + + "[" + 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_POSITION + "Accountant " + + PREFIX_STATUS + "Scheduled " + + PREFIX_TAG + "pending " + + PREFIX_TAG + "reviewRequired"; + + public static final String MESSAGE_SUCCESS = "New candidate added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "Candidate with Email:" + + " [ %1$s ] already exists in the HR Manager"; + + private final Person toAdd; + + /** + * Creates an AddCommand to add the specified {@code Person} + */ + public AddCandidateCommand(Person person) { + requireNonNull(person); + toAdd = person; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasPerson(toAdd)) { + throw new CommandException(String.format(MESSAGE_DUPLICATE_PERSON, toAdd.getEmail())); + } + + Set<Position> positions = toAdd.getPositions(); + Set<Position> positionReferences = new HashSet<>(); + for (Position p : positions) { + if (!model.hasPosition(p)) { + throw new CommandException(String.format(MESSAGE_POSITION_DOES_NOT_EXIST, p.getTitle())); + } + if (model.isPositionClosed(p)) { + throw new CommandException(String.format(MESSAGE_POSITION_CLOSED, + model.getPositionReference(p).getTitle())); + } + + positionReferences.add(model.getPositionReference(p)); + } + + toAdd.setPositions(positionReferences); + model.addPerson(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), CommandResult.CommandType.CANDIDATE); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddCandidateCommand // instanceof handles nulls + && toAdd.equals(((AddCandidateCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/candidate/DeleteCandidateCommand.java similarity index 58% rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java rename to src/main/java/seedu/address/logic/candidate/DeleteCandidateCommand.java index 02fd256acba..479550286d0 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/candidate/DeleteCandidateCommand.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.address.logic.candidate; import static java.util.Objects.requireNonNull; @@ -6,27 +6,29 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.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. + * Deletes a candidate identified using it's displayed index from the HR Manager. */ -public class DeleteCommand extends Command { +public class DeleteCandidateCommand extends Command { - public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_WORD = "delete_c"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" + + ": Deletes the candidate identified by the index number used in the displayed candidate 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"; + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Candidate: %1$s"; private final Index targetIndex; - public DeleteCommand(Index targetIndex) { + public DeleteCandidateCommand(Index targetIndex) { this.targetIndex = targetIndex; } @@ -41,13 +43,15 @@ public CommandResult execute(Model model) throws CommandException { Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + model.deletePersonFromInterview(personToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete), + CommandResult.CommandType.CANDIDATE); } @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 + || (other instanceof DeleteCandidateCommand // instanceof handles nulls + && targetIndex.equals(((DeleteCandidateCommand) other).targetIndex)); // state check } } diff --git a/src/main/java/seedu/address/logic/candidate/EditCandidateCommand.java b/src/main/java/seedu/address/logic/candidate/EditCandidateCommand.java new file mode 100644 index 00000000000..6d88768020f --- /dev/null +++ b/src/main/java/seedu/address/logic/candidate/EditCandidateCommand.java @@ -0,0 +1,357 @@ +package seedu.address.logic.candidate; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.model.position.Position.MESSAGE_POSITION_CLOSED; +import static seedu.address.model.position.Position.MESSAGE_POSITION_DOES_NOT_EXIST; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; +import seedu.address.model.person.Status; +import seedu.address.model.position.Position; +import seedu.address.model.tag.Tag; + +/** + * Edits the details of an existing candidate in the HR Manager. + */ +public class EditCandidateCommand extends Command { + + public static final String COMMAND_WORD = "edit_c"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the candidate identified " + + "by the index number used in the displayed candidate list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_STATUS + "STATUS] " + + "[" + PREFIX_TAG + "TAG]... " + + "[" + PREFIX_POSITION + "POSITION]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PHONE + "91234567 " + + PREFIX_EMAIL + "johndoe@example.com"; + + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Candidate: %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 = "Candidate with Email:" + + " [ %1$s ] already exists in the HR Manager"; + public static final String MESSAGE_ILLEGAL_PERSON_STATUS = "Unable to change Status of %1$s to APPLIED.\n" + + "Candidate %1$s already has scheduled interview(s)."; + + private final Index index; + private final EditPersonDescriptor editPersonDescriptor; + + /** + * @param index of the person in the filtered person list to edit + * @param editPersonDescriptor details to edit the person with + */ + public EditCandidateCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + requireNonNull(index); + requireNonNull(editPersonDescriptor); + + this.index = index; + this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Person> lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToEdit = lastShownList.get(index.getZeroBased()); + Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + + if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + throw new CommandException(String.format(MESSAGE_DUPLICATE_PERSON, editedPerson.getEmail())); + } + + if (editPersonDescriptor.getStatus().isPresent()) { + Status editedPersonStatus = editPersonDescriptor.getStatus().get(); + if (personToEdit.getInterviews().size() > 0 && editedPersonStatus == Status.APPLIED) { + throw new CommandException(String.format(MESSAGE_ILLEGAL_PERSON_STATUS, personToEdit.getName())); + } + } + + Set<Position> newPositions = editedPerson.getPositions(); + Set<Position> positionReferences = new HashSet<>(); + + for (Position p : newPositions) { + if (!model.hasPosition(p)) { + throw new CommandException(String.format(MESSAGE_POSITION_DOES_NOT_EXIST, p.getTitle())); + } + if (model.isPositionClosed(p)) { + throw new CommandException(String.format(MESSAGE_POSITION_CLOSED, + model.getPositionReference(p).getTitle())); + } + positionReferences.add(model.getPositionReference(p)); + } + editedPerson.setPositions(positionReferences); + + Set<Interview> interviews = personToEdit.getInterviews(); + + newPositions = positionReferences; + + // Checks if positions was edited, remove from interviews for positions that candidate no longer applies to. + if (editPersonDescriptor.isPositionEdited()) { + for (Interview i : interviews) { + i.deleteCandidate(personToEdit); + + if (!newPositions.contains(i.getPosition())) { + // delete interview from candidate if they no longer apply to the position. + editedPerson.deleteInterview(i); + } else { + // add edited person to interview, if edited candidate still applies to the position. + i.addCandidate(editedPerson); + } + } + } else { + //Remove the old person and add the new one + for (Interview i : interviews) { + i.deleteCandidate(personToEdit); + i.addCandidate(editedPerson); + } + } + + model.setPerson(personToEdit, editedPerson); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson), + CommandResult.CommandType.CANDIDATE); + } + + /** + * Creates and returns a {@code Person} with the details of {@code personToEdit} + * edited with {@code editPersonDescriptor}. + */ + public static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { + assert personToEdit != null; + + Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); + Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); + Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); + Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + Remark updatedRemark = personToEdit.getRemark(); // edit command does not allow editing remarks + Set<Tag> updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); + Status updatedStatus = editPersonDescriptor.getStatus().orElse(personToEdit.getStatus()); + Set<Position> updatedPositions = editPersonDescriptor.getPositions().orElse(personToEdit.getPositions()); + Set<Interview> interviews = personToEdit.getInterviews(); + + Person updatedPerson = new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedRemark, + updatedTags, updatedStatus, updatedPositions); + + for (Interview i : interviews) { + updatedPerson.addInterview(i); + } + + return updatedPerson; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditCandidateCommand)) { + return false; + } + + // state check + EditCandidateCommand e = (EditCandidateCommand) other; + return index.equals(e.index) + && editPersonDescriptor.equals(e.editPersonDescriptor); + } + + /** + * Stores the details to edit the candidate with. Each non-empty field value will replace the + * corresponding field value of the candidate. + */ + public static class EditPersonDescriptor { + private Name name; + private Phone phone; + private Email email; + private Address address; + private Set<Tag> tags; + private Status status; + private Set<Position> positions; + private Set<Interview> interviews; + + public EditPersonDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditPersonDescriptor(EditPersonDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setEmail(toCopy.email); + setAddress(toCopy.address); + setTags(toCopy.tags); + setStatus(toCopy.status); + setPositions(toCopy.positions); + setInterviews(toCopy.interviews); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, phone, email, address, tags, status, positions, interviews); + } + + /** + * Returns true if position field is edited. + */ + public boolean isPositionEdited() { + return CollectionUtil.isAnyNonNull(positions); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional<Name> getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional<Phone> getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional<Email> getEmail() { + return Optional.ofNullable(email); + } + + public void setAddress(Address address) { + this.address = address; + } + + public Optional<Address> getAddress() { + return Optional.ofNullable(address); + } + + public void setStatus(Status status) { + this.status = status; + } + + public Optional<Status> getStatus() { + return Optional.ofNullable(status); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set<Tag> tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional<Set<Tag>> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + /** + * Sets {@code positions} to this object's {@code positions}. + * A defensive copy of {@code positions} is used internally. + */ + public void setPositions(Set<Position> positions) { + this.positions = (positions != null) ? new HashSet<>(positions) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code positions} is null. + */ + public Optional<Set<Position>> getPositions() { + return (positions != null) ? Optional.of(Collections.unmodifiableSet(positions)) : Optional.empty(); + } + + /** + * Sets {@code positions} to this object's {@code positions}. + * A defensive copy of {@code positions} is used internally. + */ + public void setInterviews(Set<Interview> interviews) { + this.interviews = (interviews != null) ? new HashSet<>(interviews) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code positions} is null. + */ + public Optional<Set<Interview>> getInterviews() { + return (interviews != null) ? Optional.of(Collections.unmodifiableSet(interviews)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPersonDescriptor)) { + return false; + } + + // state check + EditPersonDescriptor e = (EditPersonDescriptor) other; + + return getName().equals(e.getName()) + && getPhone().equals(e.getPhone()) + && getEmail().equals(e.getEmail()) + && getAddress().equals(e.getAddress()) + && getTags().equals(e.getTags()) + && getStatus().equals(e.getStatus()) + && getPositions().equals(e.getPositions()) + && getInterviews().equals(e.getInterviews()); + } + } +} diff --git a/src/main/java/seedu/address/logic/candidate/FindCandidateCommand.java b/src/main/java/seedu/address/logic/candidate/FindCandidateCommand.java new file mode 100644 index 00000000000..5b8eed519cb --- /dev/null +++ b/src/main/java/seedu/address/logic/candidate/FindCandidateCommand.java @@ -0,0 +1,61 @@ +package seedu.address.logic.candidate; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.person.FindCandidateCommandPredicate; + + +/** + * Finds and lists all persons in HR Manager whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindCandidateCommand extends Command { + + public static final String COMMAND_WORD = "find_c"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons that contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters:" + "[" + PREFIX_NAME + "NAME]... " + + "[" + PREFIX_ADDRESS + "ADDRESS]... " + + "[" + PREFIX_EMAIL + "EMAIL]... " + + "[" + PREFIX_PHONE + "PHONE]... " + + "[" + PREFIX_POSITION + "POSITION]... " + + "[" + PREFIX_STATUS + "STATUS]... " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "alice bob charlie"; + public static final String MESSAGE_SUCCESS = "Listed all found candidates"; + + private final FindCandidateCommandPredicate predicate; + + public FindCandidateCommand(FindCandidateCommandPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()), + CommandResult.CommandType.FIND_C); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindCandidateCommand // instanceof handles nulls + && predicate.equals(((FindCandidateCommand) other).predicate)); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/candidate/ListCandidateCommand.java b/src/main/java/seedu/address/logic/candidate/ListCandidateCommand.java new file mode 100644 index 00000000000..8b39f2ea9a3 --- /dev/null +++ b/src/main/java/seedu/address/logic/candidate/ListCandidateCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.candidate; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all candidates in the HR Manager to the user. + */ +public class ListCandidateCommand extends Command { + + public static final String COMMAND_WORD = "list_c"; + + public static final String MESSAGE_SUCCESS = "Listed all candidates"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(MESSAGE_SUCCESS, CommandResult.CommandType.LIST_C); + } +} diff --git a/src/main/java/seedu/address/logic/candidate/RemarkCandidateCommand.java b/src/main/java/seedu/address/logic/candidate/RemarkCandidateCommand.java new file mode 100644 index 00000000000..b6392165253 --- /dev/null +++ b/src/main/java/seedu/address/logic/candidate/RemarkCandidateCommand.java @@ -0,0 +1,102 @@ +package seedu.address.logic.candidate; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +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.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.person.Remark; + +/** + * Changes the remark of an existing person in the HR Manager. + */ +public class RemarkCandidateCommand extends Command { + + public static final String COMMAND_WORD = "remark_c"; + public static final String MESSAGE_ARGUMENTS = "Index: %1$d, Remark: %2$s"; + public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to Person: %1$s"; + public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Person: %1$s"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits the remark of the person identified " + + "by the index number used in the last person listing. " + + "Existing remark will be overwritten by the input.\n" + + "Parameters: INDEX (must be a positive integer) " + + "remark=REMARK\n" + + "Example: " + COMMAND_WORD + " 1 " + + "remark=Likes to swim."; + private final Index index; + private final Remark remark; + + /** + * @param index of the person in the filtered person list to edit the remark + * @param remark of the person to be updated to + */ + public RemarkCandidateCommand(Index index, Remark remark) { + requireAllNonNull(index, remark); + + this.index = index; + this.remark = remark; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + List<Person> lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToEdit = lastShownList.get(index.getZeroBased()); + Person editedPerson = new Person( + personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), + personToEdit.getAddress(), remark, personToEdit.getTags(), personToEdit.getStatus(), + personToEdit.getPositions()); + + Set<Interview> personInterviews = personToEdit.getInterviews(); + for (Interview i : personInterviews) { + editedPerson.addInterview(i); + i.deleteCandidate(personToEdit); + i.addCandidate(editedPerson); + } + + model.setPerson(personToEdit, editedPerson); + + return new CommandResult(generateSuccessMessage(editedPerson), CommandResult.CommandType.CANDIDATE); + } + + /** + * Generates a command execution success message based on whether + * the remark is added to or removed from + * {@code personToEdit}. + */ + private String generateSuccessMessage(Person personToEdit) { + String message = !remark.value.isEmpty() ? MESSAGE_ADD_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS; + return String.format(message, personToEdit); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RemarkCandidateCommand)) { + return false; + } + + // state check + RemarkCandidateCommand e = (RemarkCandidateCommand) other; + return index.equals(e.index) + && remark.equals(e.remark); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/address/logic/candidate/exceptions/CommandException.java similarity index 89% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/seedu/address/logic/candidate/exceptions/CommandException.java index a16bd14f2cd..9fa4209dbf4 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/address/logic/candidate/exceptions/CommandException.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands.exceptions; +package seedu.address.logic.candidate.exceptions; /** * Represents an error which occurs during execution of a {@link Command}. diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 71656d7c5c8..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 7e36114902f..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,226 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List<Person> lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set<Tag> updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set<Tag> tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional<Name> getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional<Phone> getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional<Email> getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional<Address> getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set<Tag> tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional<Set<Tag>> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/general/ClearCommand.java b/src/main/java/seedu/address/logic/general/ClearCommand.java new file mode 100644 index 00000000000..f4e7ba47d0b --- /dev/null +++ b/src/main/java/seedu/address/logic/general/ClearCommand.java @@ -0,0 +1,25 @@ +package seedu.address.logic.general; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.model.HrManager; +import seedu.address.model.Model; + +/** + * Clears the HR Manager. + */ +public class ClearCommand extends Command { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "HR Manager has been cleared!"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.setHrManager(new HrManager()); + return new CommandResult(MESSAGE_SUCCESS, CommandResult.CommandType.GENERAL); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/general/ExitCommand.java similarity index 55% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/seedu/address/logic/general/ExitCommand.java index 3dd85a8ba90..9a159b09d6f 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/general/ExitCommand.java @@ -1,5 +1,7 @@ -package seedu.address.logic.commands; +package seedu.address.logic.general; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; import seedu.address.model.Model; /** @@ -9,11 +11,11 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting HR Manager as requested ..."; @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, CommandResult.CommandType.EXIT); } } diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/general/HelpCommand.java similarity index 71% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/seedu/address/logic/general/HelpCommand.java index bf824f91bd0..6c3fa719684 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/general/HelpCommand.java @@ -1,5 +1,7 @@ -package seedu.address.logic.commands; +package seedu.address.logic.general; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; import seedu.address.model.Model; /** @@ -16,6 +18,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, CommandResult.CommandType.HELP); } } diff --git a/src/main/java/seedu/address/logic/interview/AddInterviewCommand.java b/src/main/java/seedu/address/logic/interview/AddInterviewCommand.java new file mode 100644 index 00000000000..9754cbf16bf --- /dev/null +++ b/src/main/java/seedu/address/logic/interview/AddInterviewCommand.java @@ -0,0 +1,121 @@ +package seedu.address.logic.interview; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; +import static seedu.address.model.position.Position.MESSAGE_POSITION_CLOSED; +import static seedu.address.model.position.Position.MESSAGE_POSITION_DOES_NOT_EXIST; + +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.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; + +/** + * Adds an interview to the HR Manager. + */ +public class AddInterviewCommand extends Command { + public static final String COMMAND_WORD = "add_i"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an interview to the HR Manager.\n" + + "Parameters: " + + PREFIX_POSITION + "POSITION " + + "[" + PREFIX_CANDIDATE_INDEX + "INDEX (must be a positive integer)]... " + + PREFIX_DATE + "DD/MM/YYYY " + + PREFIX_TIME + "HHMM " + + PREFIX_DURATION + "MINUTES (must be a positive integer) \n" + + "[" + PREFIX_INTERVIEW_STATUS + "STATUS]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_POSITION + "Bookkeeper " + + PREFIX_CANDIDATE_INDEX + "1 " + + PREFIX_DATE + "15/10/2021 " + + PREFIX_TIME + "1400 " + + PREFIX_DURATION + "120 " + + PREFIX_INTERVIEW_STATUS + "pending"; + + public static final String MESSAGE_SUCCESS = "New interview added: %1$s"; + public static final String MESSAGE_DUPLICATE_INTERVIEW = "This interview already exists in the HR Manager"; + public static final String MESSAGE_CANDIDATE_DID_NOT_APPLY = "Candidate %1$s did not apply for Position %2$s"; + public static final String MESSAGE_CANDIDATE_IS_NOT_VACANT = "Candidate %1$s is not available in this period"; + + private final Interview toAdd; + private final Set<Index> indexes; + + /** + * Creates an AddInterviewCommand to add the specified {@code Interview} + */ + public AddInterviewCommand(Interview interview, Set<Index> indexSet) { + requireNonNull(interview); + toAdd = interview; + indexes = indexSet; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Person> lastShownPersonList = model.getFilteredPersonList(); + + Position position = toAdd.getPosition(); + if (!model.hasPosition(position)) { + throw new CommandException(String.format(MESSAGE_POSITION_DOES_NOT_EXIST, position.getTitle())); + } + if (model.isPositionClosed(position)) { + throw new CommandException(String.format(MESSAGE_POSITION_CLOSED, position.getTitle())); + } + toAdd.setPosition(model.getPositionReference(position)); + + //loads candidates from set of index + Set<Person> candidates = new HashSet<>(); + for (Index index : indexes) { + if (index.getZeroBased() < lastShownPersonList.size()) { + Person person = lastShownPersonList.get(index.getZeroBased()); + if (!person.appliedForPosition(position)) { + throw new CommandException(String.format(MESSAGE_CANDIDATE_DID_NOT_APPLY, + person.getName(), position)); + } + if (!person.isVacantFor(toAdd)) { + throw new CommandException(String.format(MESSAGE_CANDIDATE_IS_NOT_VACANT, person.getName())); + } + candidates.add(person); + } else { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + } + toAdd.setCandidates(candidates); + + if (model.hasInterview(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_INTERVIEW); + } + + for (Person p : model.getFilteredPersonList()) { + if (toAdd.hasCandidate(p)) { + p.addInterview(toAdd); + } + } + + model.addInterview(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd.getDisplayString()), + CommandResult.CommandType.INTERVIEW); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddInterviewCommand // instanceof handles nulls + && toAdd.equals(((AddInterviewCommand) other).toAdd)) + && indexes.equals(((AddInterviewCommand) other).indexes); + } +} diff --git a/src/main/java/seedu/address/logic/interview/AssignInterviewCommand.java b/src/main/java/seedu/address/logic/interview/AssignInterviewCommand.java new file mode 100644 index 00000000000..93baea9719d --- /dev/null +++ b/src/main/java/seedu/address/logic/interview/AssignInterviewCommand.java @@ -0,0 +1,127 @@ +package seedu.address.logic.interview; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_INDEX; + +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.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; + +public class AssignInterviewCommand extends Command { + public static final String COMMAND_WORD = "assign"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Assigns the candidates specified by their candidate index from an interview specified by its " + + "interview index.\n" + + "Parameters: " + + PREFIX_INTERVIEW_INDEX + "INTERVIEW_INDEX " + + PREFIX_CANDIDATE_INDEX + "CANDIDATE_INDEX (must be a positive integer)...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_INTERVIEW_INDEX + "1 " + + PREFIX_CANDIDATE_INDEX + "1 3 "; + + public static final String MESSAGE_SUCCESS = "Candidates added to interview %1$s: %2$s"; + public static final String MESSAGE_CANDIDATE_DID_NOT_APPLY = "Candidate %1$s (%2$s) did not apply for " + + "this position: %3$s"; + public static final String MESSAGE_CANDIDATE_HAS_INTERVIEW = "Candidate %1$s (%2$s) has already been assigned" + + " to interview %3$s."; + public static final String MESSAGE_CANDIDATE_IS_NOT_VACANT = "Candidate %1$s is not available in this period"; + + private Index interviewIndex; + + private Set<Index> candidateIndexes; + + /** + * Creates an AddInterviewCommand to add the specified {@code Interview} + */ + public AssignInterviewCommand(Index interview, Set<Index> candidates) { + requireNonNull(interview); + requireNonNull(candidates); + + interviewIndex = interview; + candidateIndexes = candidates; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Person> lastShownCandidateList = model.getFilteredPersonList(); + List<Interview> lastShownInterviewList = model.getFilteredInterviewList(); + CommandResult result; + + if (interviewIndex.getZeroBased() >= model.getFilteredInterviewList().size()) { + throw new CommandException(Messages.MESSAGE_INVALID_INTERVIEW_DISPLAYED_INDEX); + } + + Interview interview = lastShownInterviewList.get(interviewIndex.getZeroBased()); + EditInterviewCommand.EditInterviewDescriptor descriptor = new EditInterviewCommand.EditInterviewDescriptor(); + descriptor.setCandidateIndexes(candidateIndexes); + + Interview assignedInterview = EditInterviewCommand.createEditedInterview(interview, descriptor); + Set<Person> newCandidates = interview.getCandidates(); + + StringBuilder candidatesAdded = new StringBuilder(); + candidatesAdded.append("\n"); + + //checking + for (Index candidateIndex : candidateIndexes) { + if (candidateIndex.getZeroBased() >= lastShownCandidateList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Person candidate = lastShownCandidateList.get(candidateIndex.getZeroBased()); + Position position = interview.getPosition(); + + if (!candidate.appliedForPosition(position)) { + throw new CommandException(String.format(MESSAGE_CANDIDATE_DID_NOT_APPLY, + candidateIndex.getOneBased(), candidate.getName(), interview.getPositionTitle())); + } + + if (candidate.hasInterview(assignedInterview)) { + throw new CommandException(String.format(MESSAGE_CANDIDATE_HAS_INTERVIEW, + candidateIndex.getOneBased(), candidate.getName(), interview.getDisplayStringWithoutNames())); + } + + if (!candidate.isVacantFor(assignedInterview)) { + throw new CommandException(String.format(MESSAGE_CANDIDATE_IS_NOT_VACANT, candidate.getName())); + } + } + + //executing + int count = 1; + for (Index candidateIndex : candidateIndexes) { + Person candidate = lastShownCandidateList.get(candidateIndex.getZeroBased()); + + newCandidates.add(candidate); + candidate.addInterview(assignedInterview); + + candidatesAdded.append(count + ". " + candidate.getName() + "\n"); + count++; + } + + assignedInterview.setCandidates(newCandidates); + model.setInterview(interview, assignedInterview); + + result = new CommandResult(String.format(MESSAGE_SUCCESS, interview.getDisplayStringWithoutNames(), + candidatesAdded), CommandResult.CommandType.INTERVIEW); + + return result; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AssignInterviewCommand// instanceof handles nulls + && interviewIndex.equals(((AssignInterviewCommand) other).interviewIndex)); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/interview/DeleteInterviewCommand.java b/src/main/java/seedu/address/logic/interview/DeleteInterviewCommand.java new file mode 100644 index 00000000000..1b79fffeeec --- /dev/null +++ b/src/main/java/seedu/address/logic/interview/DeleteInterviewCommand.java @@ -0,0 +1,54 @@ +package seedu.address.logic.interview; + +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.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.interview.Interview; + +public class DeleteInterviewCommand extends Command { + public static final String COMMAND_WORD = "delete_i"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the interview identified by the index number used in the displayed interview list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_INTERVIEW_SUCCESS = "Deleted Interview: %1$s"; + + private final Index targetIndex; + + public DeleteInterviewCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Interview> lastShownList = model.getFilteredInterviewList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_INTERVIEW_DISPLAYED_INDEX); + } + + Interview interviewToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteInterview(interviewToDelete); + model.deleteInterviewFromPerson(interviewToDelete); + + return new CommandResult(String.format(MESSAGE_DELETE_INTERVIEW_SUCCESS, interviewToDelete.getDisplayString()), + CommandResult.CommandType.INTERVIEW); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteInterviewCommand// instanceof handles nulls + && targetIndex.equals(((DeleteInterviewCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/interview/EditInterviewCommand.java b/src/main/java/seedu/address/logic/interview/EditInterviewCommand.java new file mode 100644 index 00000000000..6d4e33a1276 --- /dev/null +++ b/src/main/java/seedu/address/logic/interview/EditInterviewCommand.java @@ -0,0 +1,268 @@ +package seedu.address.logic.interview; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; +import static seedu.address.model.position.Position.MESSAGE_POSITION_CLOSED; +import static seedu.address.model.position.Position.MESSAGE_POSITION_DOES_NOT_EXIST; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.interview.Interview; +import seedu.address.model.interview.Interview.InterviewStatus; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; + +public class EditInterviewCommand extends Command { + + public static final String COMMAND_WORD = "edit_i"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the interview identified " + + "by the index number used in the displayed interview list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_POSITION + "POSITION] " + + "[" + PREFIX_DATE + "DATE] " + + "[" + PREFIX_TIME + "TIME] " + + "[" + PREFIX_DURATION + "DURATION] " + + "[" + PREFIX_INTERVIEW_STATUS + "STATUS]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_DATE + "15/10/2021 " + + PREFIX_TIME + "1400 "; + + public static final String MESSAGE_EDIT_INTERVIEW_SUCCESS = "Edited Interview: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_INTERVIEW = "This interview already exists in the interview list."; + public static final String MESSAGE_CANDIDATE_DID_NOT_APPLY = "Candidate %1$s did not apply for position %2$s."; + + private final Index index; + private final EditInterviewDescriptor editInterviewDescriptor; + + /** + * @param index of the interview in the filtered interview list to edit + * @param editInterviewDescriptor details to edit the interview with + */ + public EditInterviewCommand(Index index, EditInterviewDescriptor editInterviewDescriptor) { + requireNonNull(index); + requireNonNull(editInterviewDescriptor); + + this.index = index; + this.editInterviewDescriptor = new EditInterviewDescriptor(editInterviewDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Interview> lastShownList = model.getFilteredInterviewList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_INTERVIEW_DISPLAYED_INDEX); + } + + Interview interviewToEdit = lastShownList.get(index.getZeroBased()); + Interview editedInterview = createEditedInterview(interviewToEdit, editInterviewDescriptor); + + if (!interviewToEdit.isSameInterview(editedInterview) && model.hasInterview(editedInterview)) { + throw new CommandException(MESSAGE_DUPLICATE_INTERVIEW); + } + + Position newPosition = editedInterview.getPosition(); + if (!model.hasPosition(newPosition)) { + throw new CommandException(String.format(MESSAGE_POSITION_DOES_NOT_EXIST, newPosition.getTitle())); + } + if (model.isPositionClosed(newPosition)) { + throw new CommandException(String.format(MESSAGE_POSITION_CLOSED, + model.getPositionReference(newPosition).getTitle())); + } + + for (Person candidate : interviewToEdit.getCandidates()) { + if (!candidate.appliedForPosition(newPosition)) { + throw new CommandException(String.format(MESSAGE_CANDIDATE_DID_NOT_APPLY, + candidate.getName(), editedInterview.getPosition())); + } + } + + for (Person candidate : interviewToEdit.getCandidates()) { + candidate.deleteInterview(interviewToEdit); + candidate.addInterview(editedInterview); + } + + editedInterview.setPosition(model.getPositionReference(newPosition)); + model.setInterview(interviewToEdit, editedInterview); + + return new CommandResult(String.format(MESSAGE_EDIT_INTERVIEW_SUCCESS, editedInterview.getDisplayString()), + CommandResult.CommandType.INTERVIEW); + } + + /** + * Creates and returns a {@code Interview} with the details of {@code interviewToEdit} + * edited with {@code editInterviewDescriptor}. + */ + public static Interview createEditedInterview( + Interview interviewToEdit, EditInterviewDescriptor editInterviewDescriptor) { + assert interviewToEdit != null; + + Position updatedPosition = editInterviewDescriptor.getPosition().orElse(interviewToEdit.getPosition()); + Set<Person> initialCandidates = interviewToEdit.getCandidates(); // candidates cannot be edited in edit_i + LocalDate updatedDate = editInterviewDescriptor.getDate().orElse(interviewToEdit.getDate()); + LocalTime updatedTime = editInterviewDescriptor.getStartTime().orElse(interviewToEdit.getStartTime()); + Duration updatedDuration = editInterviewDescriptor.getDuration().orElse(interviewToEdit.getDuration()); + InterviewStatus updatedStatus = editInterviewDescriptor.getStatus().orElse(interviewToEdit.getStatus()); + + return new Interview(updatedPosition, initialCandidates, updatedDate, + updatedTime, updatedDuration, updatedStatus); //new interview with all fields except candidates updated + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditInterviewCommand)) { + return false; + } + + // state check + EditInterviewCommand e = (EditInterviewCommand) other; + return index.equals(e.index) + && editInterviewDescriptor.equals(e.editInterviewDescriptor); + } + + /** + * Stores the details to edit the candidate with. Each non-empty field value will replace the + * corresponding field value of the candidate. + */ + public static class EditInterviewDescriptor { + private Position position; + private Set<Index> candidateIndexes; + private LocalDate date; + private LocalTime startTime; + private Duration duration; + private InterviewStatus status; + + public EditInterviewDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditInterviewDescriptor(EditInterviewDescriptor toCopy) { + setPosition(toCopy.position); + setCandidateIndexes(toCopy.candidateIndexes); + setDate(toCopy.date); + setStartTime(toCopy.startTime); + setDuration(toCopy.duration); + setStatus(toCopy.status); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(position, candidateIndexes, date, startTime, duration, status); + } + + public void setPosition(Position position) { + this.position = position; + } + + public Optional<Position> getPosition() { + return Optional.ofNullable(position); + } + + /** + * Sets {@code candidateIndexes} to this object's {@code candidateIndexes}. + * A defensive copy of {@code candidateIndexes} is used internally. + */ + public void setCandidateIndexes(Set<Index> candidatesIndex) { + this.candidateIndexes = (candidatesIndex != null) ? new HashSet<>(candidatesIndex) : null; + } + + /** + * Returns an unmodifiable candidateIndex set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code candidates} is null. + */ + public Optional<Set<Index>> getCandidateIndexes() { + return (candidateIndexes != null) + ? Optional.of(Collections.unmodifiableSet(candidateIndexes)) + : Optional.empty(); + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public Optional<LocalDate> getDate() { + return Optional.ofNullable(date); + } + + public void setStartTime(LocalTime startTime) { + this.startTime = startTime; + } + + public Optional<LocalTime> getStartTime() { + return Optional.ofNullable(startTime); + } + + public void setDuration(Duration duration) { + this.duration = duration; + } + + public Optional<Duration> getDuration() { + return Optional.ofNullable(duration); + } + + public void setStatus(InterviewStatus status) { + this.status = status; + } + + public Optional<InterviewStatus> getStatus() { + return Optional.ofNullable(status); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditInterviewDescriptor)) { + return false; + } + + // state check + EditInterviewDescriptor e = (EditInterviewDescriptor) other; + + return getPosition().equals(e.getPosition()) + && getCandidateIndexes().equals(e.getCandidateIndexes()) + && getDate().equals(e.getDate()) + && getStartTime().equals(e.getStartTime()) + && getDuration().equals(e.getDuration()) + && getStatus().equals(e.getStatus()); + } + } +} diff --git a/src/main/java/seedu/address/logic/interview/FindInterviewCommand.java b/src/main/java/seedu/address/logic/interview/FindInterviewCommand.java new file mode 100644 index 00000000000..e4e1d71a2c3 --- /dev/null +++ b/src/main/java/seedu/address/logic/interview/FindInterviewCommand.java @@ -0,0 +1,61 @@ +package seedu.address.logic.interview; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.interview.FindInterviewCommandPredicate; + + +/** + * Finds and lists all persons in HR Manager whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindInterviewCommand extends Command { + + public static final String COMMAND_WORD = "find_i"; + + public static final String MESSAGE_SUCCESS = "Listed all found interviews"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all interviews that contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: " + + "[" + PREFIX_POSITION + "TITLE]... " + + "[" + PREFIX_INTERVIEW_STATUS + "INTERVIEW STATUS]... " + + "[" + PREFIX_TIME + "TIME]... " + + "[" + PREFIX_DATE + "DATE]... " + + "[" + PREFIX_CANDIDATE_INDEX + "CANDIDATE NAME]...\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_POSITION + "accountant" + " " + PREFIX_DATE + "21/09/2021"; + private final FindInterviewCommandPredicate predicate; + + + + public FindInterviewCommand(FindInterviewCommandPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredInterviewList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_INTERVIEW_LISTED_OVERVIEW, model.getFilteredInterviewList().size()), + CommandResult.CommandType.FIND_I); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindInterviewCommand // instanceof handles nulls + && predicate.equals(((FindInterviewCommand) other).predicate)); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/interview/ListInterviewCommand.java b/src/main/java/seedu/address/logic/interview/ListInterviewCommand.java new file mode 100644 index 00000000000..81c9bfdc221 --- /dev/null +++ b/src/main/java/seedu/address/logic/interview/ListInterviewCommand.java @@ -0,0 +1,24 @@ +package seedu.address.logic.interview; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_INTERVIEWS; + +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all interviews in the HR Manager to the user. + */ +public class ListInterviewCommand extends Command { + public static final String COMMAND_WORD = "list_i"; + + public static final String MESSAGE_SUCCESS = "Listed all interviews"; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredInterviewList(PREDICATE_SHOW_ALL_INTERVIEWS); + return new CommandResult(MESSAGE_SUCCESS, CommandResult.CommandType.LIST_I); + } +} diff --git a/src/main/java/seedu/address/logic/interview/UnassignInterviewCommand.java b/src/main/java/seedu/address/logic/interview/UnassignInterviewCommand.java new file mode 100644 index 00000000000..aa4a82e7ddf --- /dev/null +++ b/src/main/java/seedu/address/logic/interview/UnassignInterviewCommand.java @@ -0,0 +1,147 @@ +package seedu.address.logic.interview; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_INDEX; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; + +public class UnassignInterviewCommand extends Command { + public static final String COMMAND_WORD = "unassign"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Unassigns the candidates specified by their candidate index from an interview specified by its " + + "interview index.\n" + + "Parameters: " + + PREFIX_INTERVIEW_INDEX + "INTERVIEW_INDEX " + + PREFIX_CANDIDATE_INDEX + "CANDIDATE_INDEX (must be a positive integer)...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_INTERVIEW_INDEX + "1 " + + PREFIX_CANDIDATE_INDEX + "1 3 "; + + public static final String MESSAGE_SUCCESS = "Candidates removed from interview %1$s: %2$s"; + public static final String MESSAGE_CANDIDATE_DID_NOT_APPLY = "Candidate %1$s (%2$s) is not scheduled for " + + "this Interview: %3$s"; + public static final String MESSAGE_ALL_CANDIDATES_REMOVED = "All candidates removed from interview: %1$s"; + public static final String MESSAGE_CANDIDATE_INDEX_NOT_VALID = "The person index %1$s is invalid"; + + private Index interviewIndex; + + private Set<Index> candidateIndexes; + + private boolean isTotalWipe; + + /** + * Creates an AddInterviewCommand to add the specified {@code Interview} + */ + public UnassignInterviewCommand(Index interview, Set<Index> candidates) { + requireNonNull(interview); + requireNonNull(candidates); + + interviewIndex = interview; + candidateIndexes = candidates; + isTotalWipe = false; + } + + /** + * Creates an AddInterviewCommand to add the specified {@code Interview} + */ + public UnassignInterviewCommand(Index interview, boolean isTotalWipe) { + requireNonNull(interview); + requireNonNull(isTotalWipe); + + interviewIndex = interview; + candidateIndexes = new HashSet<>(); + this.isTotalWipe = isTotalWipe; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Person> lastShownCandidateList = model.getFilteredPersonList(); + List<Interview> lastShownInterviewList = model.getFilteredInterviewList(); + CommandResult result; + + if (interviewIndex.getZeroBased() >= model.getFilteredInterviewList().size()) { + throw new CommandException(Messages.MESSAGE_INVALID_INTERVIEW_DISPLAYED_INDEX); + } + + Interview interviewToUnassign = lastShownInterviewList.get(interviewIndex.getZeroBased()); + + for (Index candidateIndex : candidateIndexes) { + if (candidateIndex.getZeroBased() >= lastShownCandidateList.size()) { + throw new CommandException(String.format(MESSAGE_CANDIDATE_INDEX_NOT_VALID, + candidateIndex.getOneBased())); + } + + Person candidate = lastShownCandidateList.get(candidateIndex.getZeroBased()); + if (!candidate.hasInterview(interviewToUnassign)) { + throw new CommandException(String.format(MESSAGE_CANDIDATE_DID_NOT_APPLY, + candidateIndex.getOneBased(), candidate.getName(), interviewToUnassign.getDisplayString())); + } + } + + if (isTotalWipe) { + Set<Person> emptyCandidatesSet = new HashSet<>(); + interviewToUnassign.setCandidates(emptyCandidatesSet); + model.deleteInterviewFromPerson(interviewToUnassign); + result = new CommandResult(String.format(MESSAGE_ALL_CANDIDATES_REMOVED, + interviewToUnassign.getDisplayStringWithoutNames()), + CommandResult.CommandType.INTERVIEW); + } else { + int count = 1; + StringBuilder removedPersons = new StringBuilder(); + removedPersons.append("\n"); + + for (Index candidateIndex : candidateIndexes) { + Person candidate = lastShownCandidateList.get(candidateIndex.getZeroBased()); + interviewToUnassign.deleteCandidate(candidate); + candidate.deleteInterview(interviewToUnassign); + + removedPersons.append(count + ". " + candidate.getName() + "\n"); + count++; + } + + result = new CommandResult(String.format(MESSAGE_SUCCESS, + interviewToUnassign.getDisplayStringWithoutNames(), removedPersons), + CommandResult.CommandType.INTERVIEW); + } + + return result; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnassignInterviewCommand// instanceof handles nulls + && interviewIndex.equals(((UnassignInterviewCommand) other).interviewIndex)) // state check + && isTotalWipe == ((((UnassignInterviewCommand) other).isTotalWipe)); + + } + + /** + * Returns an unmodifiable candidateIndex set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code candidates} is null. + */ + public Optional<Set<Index>> getCandidateIndexes() { + return (candidateIndexes != null) + ? Optional.of(Collections.unmodifiableSet(candidateIndexes)) + : Optional.empty(); + } +} + + diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCandidateCommandParser.java similarity index 67% rename from src/main/java/seedu/address/logic/parser/AddCommandParser.java rename to src/main/java/seedu/address/logic/parser/AddCandidateCommandParser.java index 3b8bfa035e8..7af35df9b2a 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCandidateCommandParser.java @@ -5,48 +5,59 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Set; import java.util.stream.Stream; -import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.candidate.AddCandidateCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; +import seedu.address.model.person.Status; +import seedu.address.model.position.Position; import seedu.address.model.tag.Tag; /** * Parses input arguments and creates a new AddCommand object */ -public class AddCommandParser implements Parser<AddCommand> { +public class AddCandidateCommandParser implements Parser<AddCandidateCommand> { /** * Parses the given {@code String} of arguments in the context of the AddCommand * and returns an AddCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ - public AddCommand parse(String args) throws ParseException { + public AddCandidateCommand 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_TAG, + PREFIX_STATUS, PREFIX_POSITION); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_POSITION) || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCandidateCommand.MESSAGE_USAGE)); } Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + Remark remark = new Remark(""); // add command does not allow adding remarks straight away Set<Tag> tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Set<Position> positionList = ParserUtil.parsePositions(argMultimap.getAllValues(PREFIX_POSITION)); - Person person = new Person(name, phone, email, address, tagList); + Status status = ParserUtil.parseStatus(argMultimap.getValue(PREFIX_STATUS).orElse("")); - return new AddCommand(person); + Person person = new Person(name, phone, email, address, remark, tagList, status, positionList); + + return new AddCandidateCommand(person); } /** diff --git a/src/main/java/seedu/address/logic/parser/AddInterviewCommandParser.java b/src/main/java/seedu/address/logic/parser/AddInterviewCommandParser.java new file mode 100644 index 00000000000..b6bfafe4263 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddInterviewCommandParser.java @@ -0,0 +1,71 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.interview.AddInterviewCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.interview.Interview; +import seedu.address.model.interview.Interview.InterviewStatus; +import seedu.address.model.position.Position; + +public class AddInterviewCommandParser implements Parser<AddInterviewCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the AddInterviewCommand + * and returns an AddInterviewCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddInterviewCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_POSITION, PREFIX_CANDIDATE_INDEX, PREFIX_DATE, PREFIX_TIME, + PREFIX_DURATION, PREFIX_INTERVIEW_STATUS); + + if (!arePrefixesPresent(argMultimap, PREFIX_POSITION, PREFIX_DATE, PREFIX_TIME, + PREFIX_DURATION) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddInterviewCommand.MESSAGE_USAGE)); + } + + Position position = ParserUtil.parsePosition(argMultimap.getValue(PREFIX_POSITION).get()); + LocalDate date = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get()); + LocalTime time = ParserUtil.parseTime(argMultimap.getValue(PREFIX_TIME).get()); + Duration duration = ParserUtil.parseDuration(argMultimap.getValue(PREFIX_DURATION).get()); + InterviewStatus interviewStatus = ParserUtil.parseInterviewStatus(argMultimap + .getValue(PREFIX_INTERVIEW_STATUS).orElse("")); + + Set<Index> indexes; + String candidateIndexes = argMultimap.getValue(PREFIX_CANDIDATE_INDEX).orElse(""); + if (candidateIndexes.equals("")) { + indexes = new HashSet<>(); + } else { + indexes = ParserUtil.parseCandidateIndexes(candidateIndexes); + } + + Interview interview = new Interview(position, new HashSet<>(), date, time, duration, interviewStatus); + + return new AddInterviewCommand(interview, indexes); + } + + /** + * 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/AddPositionCommandParser.java b/src/main/java/seedu/address/logic/parser/AddPositionCommandParser.java new file mode 100644 index 00000000000..6a171073d95 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddPositionCommandParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.util.stream.Stream; + +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.position.AddPositionCommand; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; + +public class AddPositionCommandParser implements Parser<AddPositionCommand> { + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddPositionCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TITLE); + if (!arePrefixesPresent(argMultimap, PREFIX_TITLE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPositionCommand.MESSAGE_USAGE)); + } + + Title title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE).get()); + Position position = new Position(title); + return new AddPositionCommand(position); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 1e466792b46..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<commandWord>\\S+)(?<arguments>.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AssignInterviewCommandParser.java b/src/main/java/seedu/address/logic/parser/AssignInterviewCommandParser.java new file mode 100644 index 00000000000..da8e726d0b4 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AssignInterviewCommandParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_INDEX; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.interview.AssignInterviewCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class AssignInterviewCommandParser implements Parser<AssignInterviewCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the AssignInterviewCommand + * and returns an AssignInterviewCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format. + */ + @Override + public AssignInterviewCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_CANDIDATE_INDEX, PREFIX_INTERVIEW_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_CANDIDATE_INDEX, PREFIX_INTERVIEW_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AssignInterviewCommand.MESSAGE_USAGE)); + } + + Index interviewIndex; + try { + interviewIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INTERVIEW_INDEX).orElse("")); + } catch (ParseException pe) { + throw new ParseException(ParserUtil.MESSAGE_EMPTY_INTERVIEW_INDEXES); + } + + Set<Index> candidateIndexes; + String candidateIndexInput = argMultimap.getValue(PREFIX_CANDIDATE_INDEX).orElse(""); + + candidateIndexes = ParserUtil.parseCandidateIndexes(candidateIndexInput); + return new AssignInterviewCommand(interviewIndex, candidateIndexes); + + } + + /** + * 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/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..786beb83654 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -4,12 +4,22 @@ * Contains Command Line Interface (CLI) syntax definitions common to multiple commands */ public class CliSyntax { - /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_NAME = new Prefix("name="); + public static final Prefix PREFIX_PHONE = new Prefix("phone="); + public static final Prefix PREFIX_EMAIL = new Prefix("email="); + public static final Prefix PREFIX_ADDRESS = new Prefix("address="); + public static final Prefix PREFIX_TAG = new Prefix("tag="); + public static final Prefix PREFIX_REMARK = new Prefix("remark="); + public static final Prefix PREFIX_STATUS = new Prefix("status="); + public static final Prefix PREFIX_TITLE = new Prefix("title="); + public static final Prefix PREFIX_POSITION = new Prefix("position="); + public static final Prefix PREFIX_POSITION_STATUS = new Prefix("status="); + public static final Prefix PREFIX_CANDIDATE_INDEX = new Prefix("c="); + public static final Prefix PREFIX_INTERVIEW_INDEX = new Prefix("i="); + public static final Prefix PREFIX_DATE = new Prefix("date="); + public static final Prefix PREFIX_TIME = new Prefix("time="); + public static final Prefix PREFIX_DURATION = new Prefix("duration="); + public static final Prefix PREFIX_INTERVIEW_STATUS = new Prefix("interviewed="); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCandidateCommandParser.java similarity index 59% rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java rename to src/main/java/seedu/address/logic/parser/DeleteCandidateCommandParser.java index 522b93081cc..ee6e807b5f0 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCandidateCommandParser.java @@ -3,26 +3,26 @@ 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.candidate.DeleteCandidateCommand; import seedu.address.logic.parser.exceptions.ParseException; /** * Parses input arguments and creates a new DeleteCommand object */ -public class DeleteCommandParser implements Parser<DeleteCommand> { +public class DeleteCandidateCommandParser implements Parser<DeleteCandidateCommand> { /** * 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 { + public DeleteCandidateCommand parse(String args) throws ParseException { try { Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + return new DeleteCandidateCommand(index); } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getMessage()) + + DeleteCandidateCommand.MESSAGE_USAGE, pe); } } diff --git a/src/main/java/seedu/address/logic/parser/DeleteInterviewCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteInterviewCommandParser.java new file mode 100644 index 00000000000..ed878cf58e6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteInterviewCommandParser.java @@ -0,0 +1,25 @@ +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.interview.DeleteInterviewCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class DeleteInterviewCommandParser implements Parser<DeleteInterviewCommand> { + + /** + * 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 DeleteInterviewCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteInterviewCommand(index); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getMessage()) + + DeleteInterviewCommand.MESSAGE_USAGE, pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/DeletePositionCommandParser.java b/src/main/java/seedu/address/logic/parser/DeletePositionCommandParser.java new file mode 100644 index 00000000000..0e688867c6e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeletePositionCommandParser.java @@ -0,0 +1,27 @@ +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.parser.exceptions.ParseException; +import seedu.address.logic.position.DeletePositionCommand; + +/** + * Parses input arguments and creates a new DeletePositionCommand object + */ +public class DeletePositionCommandParser implements Parser<DeletePositionCommand> { + /** + * Parses the given {@code String} of arguments in the context of the DeletePositionCommand + * and returns a DeletePositionCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeletePositionCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeletePositionCommand(index); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getMessage()) + + DeletePositionCommand.MESSAGE_USAGE, pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCandidateCommandParser.java similarity index 61% rename from src/main/java/seedu/address/logic/parser/EditCommandParser.java rename to src/main/java/seedu/address/logic/parser/EditCandidateCommandParser.java index 845644b7dea..a7428dfac96 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCandidateCommandParser.java @@ -6,6 +6,8 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Collection; @@ -14,32 +16,34 @@ import java.util.Set; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.candidate.EditCandidateCommand; +import seedu.address.logic.candidate.EditCandidateCommand.EditPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.position.Position; import seedu.address.model.tag.Tag; /** * Parses input arguments and creates a new EditCommand object */ -public class EditCommandParser implements Parser<EditCommand> { +public class EditCandidateCommandParser implements Parser<EditCandidateCommand> { /** * Parses the given {@code String} of arguments in the context of the EditCommand * and returns an EditCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ - public EditCommand parse(String args) throws ParseException { + public EditCandidateCommand 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_ADDRESS, PREFIX_TAG, + PREFIX_STATUS, PREFIX_POSITION); 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(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getMessage()) + + EditCandidateCommand.MESSAGE_USAGE, pe); } EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); @@ -52,16 +56,21 @@ 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_STATUS).isPresent()) { + editPersonDescriptor.setStatus(ParserUtil.parseStatus(argMultimap.getValue(PREFIX_STATUS).orElse(""))); + } if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + parsePositionsForEdit(argMultimap.getAllValues(PREFIX_POSITION)).ifPresent(editPersonDescriptor::setPositions); + if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + throw new ParseException(EditCandidateCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); + return new EditCandidateCommand(index, editPersonDescriptor); } /** @@ -79,4 +88,21 @@ private Optional<Set<Tag>> parseTagsForEdit(Collection<String> tags) throws Pars return Optional.of(ParserUtil.parseTags(tagSet)); } + /** + * Parses {@code Collection<String> positions} into a {@code Set<Position>} if {@code positions} is non-empty. + * If {@code positions} contain only one element which is an empty string, it will be parsed into a + * {@code Set<Position>} containing zero tags. + */ + private Optional<Set<Position>> parsePositionsForEdit(Collection<String> positions) throws ParseException { + assert positions != null; + + if (positions.isEmpty()) { + return Optional.empty(); + } + Collection<String> positionSet = positions.size() == 1 && positions.contains("") + ? Collections.emptySet() + : positions; + return Optional.of(ParserUtil.parsePositions(positionSet)); + } + } diff --git a/src/main/java/seedu/address/logic/parser/EditInterviewCommandParser.java b/src/main/java/seedu/address/logic/parser/EditInterviewCommandParser.java new file mode 100644 index 00000000000..ec4608bc76b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditInterviewCommandParser.java @@ -0,0 +1,64 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.interview.EditInterviewCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class EditInterviewCommandParser implements Parser<EditInterviewCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the EditInterviewCommand + * and returns an EditInterviewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditInterviewCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_POSITION, PREFIX_DATE, PREFIX_TIME, + PREFIX_DURATION, PREFIX_INTERVIEW_STATUS); + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getMessage()) + + EditInterviewCommand.MESSAGE_USAGE, pe); + } + + EditInterviewCommand.EditInterviewDescriptor editInterviewDescriptor = + new EditInterviewCommand.EditInterviewDescriptor(); + + if (argMultimap.getValue(PREFIX_POSITION).isPresent()) { + editInterviewDescriptor.setPosition(ParserUtil.parsePosition(argMultimap.getValue(PREFIX_POSITION).get())); + } + if (argMultimap.getValue(PREFIX_DATE).isPresent()) { + editInterviewDescriptor.setDate(ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get())); + } + if (argMultimap.getValue(PREFIX_TIME).isPresent()) { + editInterviewDescriptor + .setStartTime(ParserUtil.parseTime(argMultimap.getValue(PREFIX_TIME).orElse(""))); + } + if (argMultimap.getValue(PREFIX_DURATION).isPresent()) { + editInterviewDescriptor.setDuration(ParserUtil.parseDuration(argMultimap.getValue(PREFIX_DURATION).get())); + } + if (argMultimap.getValue(PREFIX_INTERVIEW_STATUS).isPresent()) { + editInterviewDescriptor.setStatus(ParserUtil + .parseInterviewStatus(argMultimap.getValue(PREFIX_INTERVIEW_STATUS).get())); + } + + if (!editInterviewDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditInterviewCommand.MESSAGE_NOT_EDITED); + } + + return new EditInterviewCommand(index, editInterviewDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/EditPositionCommandParser.java b/src/main/java/seedu/address/logic/parser/EditPositionCommandParser.java new file mode 100644 index 00000000000..bcf54a0c2b0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditPositionCommandParser.java @@ -0,0 +1,46 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.position.EditPositionCommand; + +public class EditPositionCommandParser implements Parser<EditPositionCommand> { + + @Override + public EditPositionCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_POSITION_STATUS); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, pe.getMessage()) + + EditPositionCommand.MESSAGE_USAGE, pe); + } + + EditPositionCommand.EditPositionDescriptor editPositionDescriptor = + new EditPositionCommand.EditPositionDescriptor(); + + if (argMultimap.getValue(PREFIX_TITLE).isPresent()) { + editPositionDescriptor.setTitle(ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE).get())); + } + if (argMultimap.getValue(PREFIX_POSITION_STATUS).isPresent()) { + editPositionDescriptor.setPositionStatus(ParserUtil + .parsePositionStatus(argMultimap.getValue(PREFIX_POSITION_STATUS).get())); + } + + if (!editPositionDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditPositionCommand.MESSAGE_NOT_EDITED); + } + + return new EditPositionCommand(index, editPositionDescriptor); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCandidateCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCandidateCommandParser.java new file mode 100644 index 00000000000..6cd9002a6a3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindCandidateCommandParser.java @@ -0,0 +1,73 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.address.logic.candidate.FindCandidateCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.FindCandidateCommandPredicate; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindCandidateCommandParser implements Parser<FindCandidateCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the FindCommand + * and returns a FindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindCandidateCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG, + PREFIX_STATUS, PREFIX_POSITION); + + + FindCandidateCommandPredicate findCandidateCommandPredicate = new FindCandidateCommandPredicate(); + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + findCandidateCommandPredicate.setNameKeywords(ParserUtil.parseKeywords(argMultimap.getValue(PREFIX_NAME) + .get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + findCandidateCommandPredicate.setPhoneKeywords(ParserUtil.parseKeywords(argMultimap.getValue(PREFIX_PHONE) + .get())); + } + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + findCandidateCommandPredicate.setEmailKeywords(ParserUtil.parseKeywords(argMultimap.getValue(PREFIX_EMAIL) + .get())); + } + if (argMultimap.getValue(PREFIX_STATUS).isPresent()) { + findCandidateCommandPredicate.setStatusKeywords( + ParserUtil.parseKeywords(argMultimap.getValue(PREFIX_STATUS).orElse(""))); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + findCandidateCommandPredicate.setAddressKeywords(ParserUtil.parseKeywords( + argMultimap.getValue(PREFIX_ADDRESS).get())); + } + if (argMultimap.getValue(PREFIX_TAG).isPresent()) { + findCandidateCommandPredicate.setTagKeywords(ParserUtil.parseKeywords(argMultimap.getValue(PREFIX_TAG) + .get())); + } + + if (argMultimap.getValue(PREFIX_POSITION).isPresent()) { + findCandidateCommandPredicate.setPositionKeywords(ParserUtil.parseKeywords( + argMultimap.getValue(PREFIX_POSITION).get())); + } + + if (!findCandidateCommandPredicate.isAnyField()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCandidateCommand.MESSAGE_USAGE)); + } + + return new FindCandidateCommand(findCandidateCommandPredicate); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index 4fb71f23103..00000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser<FindCommand> { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindInterviewCommandParser.java b/src/main/java/seedu/address/logic/parser/FindInterviewCommandParser.java new file mode 100644 index 00000000000..507b8f5b0c5 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindInterviewCommandParser.java @@ -0,0 +1,72 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import seedu.address.logic.interview.FindInterviewCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.interview.FindInterviewCommandPredicate; + + + + + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindInterviewCommandParser implements Parser<FindInterviewCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the FindCommand + * and returns a FindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindInterviewCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_CANDIDATE_INDEX, PREFIX_DATE, PREFIX_INTERVIEW_STATUS, + PREFIX_POSITION, PREFIX_TIME); + + + FindInterviewCommandPredicate findInterviewCommandPredicate = new FindInterviewCommandPredicate(); + + if (argMultimap.getValue(PREFIX_CANDIDATE_INDEX).isPresent()) { + findInterviewCommandPredicate.setCandidateKeywords(ParserUtil.parseKeywords(argMultimap + .getValue(PREFIX_CANDIDATE_INDEX) + .get())); + } + + if (argMultimap.getValue(PREFIX_DATE).isPresent()) { + findInterviewCommandPredicate.setDateKeywords( + ParserUtil.parseKeywords(argMultimap.getValue(PREFIX_DATE).get())); + } + + if (argMultimap.getValue(PREFIX_INTERVIEW_STATUS).isPresent()) { + findInterviewCommandPredicate.setInterviewStatusKeywords( + ParserUtil.parseKeywords(argMultimap.getValue(PREFIX_INTERVIEW_STATUS).get())); + } + + if (argMultimap.getValue(PREFIX_POSITION).isPresent()) { + findInterviewCommandPredicate.setPositionKeywords( + ParserUtil.parseKeywords(argMultimap.getValue(PREFIX_POSITION).get())); + } + + if (argMultimap.getValue(PREFIX_TIME).isPresent()) { + findInterviewCommandPredicate.setTimeKeywords( + ParserUtil.parseTimeKeywords(argMultimap.getValue(PREFIX_TIME).get())); + } + + + if (!findInterviewCommandPredicate.isAnyField()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindInterviewCommand.MESSAGE_USAGE)); + } + + return new FindInterviewCommand(findInterviewCommandPredicate); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FindPositionCommandParser.java b/src/main/java/seedu/address/logic/parser/FindPositionCommandParser.java new file mode 100644 index 00000000000..a4853da3f41 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindPositionCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.position.FindPositionCommand; +import seedu.address.model.position.FindPositionCommandPredicate; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindPositionCommandParser implements Parser<FindPositionCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the FindCommand + * and returns a FindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindPositionCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_POSITION_STATUS); + + + FindPositionCommandPredicate findPositionCommandPredicate = new FindPositionCommandPredicate(); + + if (argMultimap.getValue(PREFIX_TITLE).isPresent()) { + findPositionCommandPredicate.setTitleKeywords(ParserUtil.parseKeywords(argMultimap.getValue(PREFIX_TITLE) + .get())); + } + + if (argMultimap.getValue(PREFIX_POSITION_STATUS).isPresent()) { + findPositionCommandPredicate.setPositionStatusKeywords( + ParserUtil.parseKeywords(argMultimap.getValue(PREFIX_POSITION_STATUS).get())); + } + + + if (!findPositionCommandPredicate.isAnyField()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPositionCommand.MESSAGE_USAGE)); + } + + return new FindPositionCommand(findPositionCommandPredicate); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/HrManagerParser.java b/src/main/java/seedu/address/logic/parser/HrManagerParser.java new file mode 100644 index 00000000000..2ad612d39b8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/HrManagerParser.java @@ -0,0 +1,127 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.Command; +import seedu.address.logic.candidate.AddCandidateCommand; +import seedu.address.logic.candidate.DeleteCandidateCommand; +import seedu.address.logic.candidate.EditCandidateCommand; +import seedu.address.logic.candidate.FindCandidateCommand; +import seedu.address.logic.candidate.ListCandidateCommand; +import seedu.address.logic.candidate.RemarkCandidateCommand; +import seedu.address.logic.general.ClearCommand; +import seedu.address.logic.general.ExitCommand; +import seedu.address.logic.general.HelpCommand; +import seedu.address.logic.interview.AddInterviewCommand; +import seedu.address.logic.interview.AssignInterviewCommand; +import seedu.address.logic.interview.DeleteInterviewCommand; +import seedu.address.logic.interview.EditInterviewCommand; +import seedu.address.logic.interview.FindInterviewCommand; +import seedu.address.logic.interview.ListInterviewCommand; +import seedu.address.logic.interview.UnassignInterviewCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.position.AddPositionCommand; +import seedu.address.logic.position.DeletePositionCommand; +import seedu.address.logic.position.EditPositionCommand; +import seedu.address.logic.position.FindPositionCommand; +import seedu.address.logic.position.ListPositionCommand; + +/** + * Parses user input. + */ +public class HrManagerParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<commandWord>\\S+)(?<arguments>.*)"); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case AddCandidateCommand.COMMAND_WORD: + return new AddCandidateCommandParser().parse(arguments); + + case EditCandidateCommand.COMMAND_WORD: + return new EditCandidateCommandParser().parse(arguments); + + case DeleteCandidateCommand.COMMAND_WORD: + return new DeleteCandidateCommandParser().parse(arguments); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case FindCandidateCommand.COMMAND_WORD: + return new FindCandidateCommandParser().parse(arguments); + + case ListCandidateCommand.COMMAND_WORD: + return new ListCandidateCommand(); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case RemarkCandidateCommand.COMMAND_WORD: + return new RemarkCandidateCommandParser().parse(arguments); + + case AddPositionCommand.COMMAND_WORD: + return new AddPositionCommandParser().parse(arguments); + + case DeletePositionCommand.COMMAND_WORD: + return new DeletePositionCommandParser().parse(arguments); + + case EditPositionCommand.COMMAND_WORD: + return new EditPositionCommandParser().parse(arguments); + + case ListPositionCommand.COMMAND_WORD: + return new ListPositionCommand(); + + case FindPositionCommand.COMMAND_WORD: + return new FindPositionCommandParser().parse(arguments); + + case AddInterviewCommand.COMMAND_WORD: + return new AddInterviewCommandParser().parse(arguments); + + case EditInterviewCommand.COMMAND_WORD: + return new EditInterviewCommandParser().parse(arguments); + + case ListInterviewCommand.COMMAND_WORD: + return new ListInterviewCommand(); + + case DeleteInterviewCommand.COMMAND_WORD: + return new DeleteInterviewCommandParser().parse(arguments); + + case FindInterviewCommand.COMMAND_WORD: + return new FindInterviewCommandParser().parse(arguments); + + case UnassignInterviewCommand.COMMAND_WORD: + return new UnassignInterviewCommandParser().parse(arguments); + + case AssignInterviewCommand.COMMAND_WORD: + return new AssignInterviewCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java index d6551ad8e3f..8a71dc65794 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/address/logic/parser/Parser.java @@ -1,6 +1,6 @@ package seedu.address.logic.parser; -import seedu.address.logic.commands.Command; +import seedu.address.logic.Command; import seedu.address.logic.parser.exceptions.ParseException; /** diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..554762437e0 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,17 +2,33 @@ import static java.util.Objects.requireNonNull; +import java.time.DateTimeException; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.interview.Interview; +import seedu.address.model.interview.Interview.InterviewStatus; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Status; +import seedu.address.model.position.Position; +import seedu.address.model.position.Position.PositionStatus; +import seedu.address.model.position.Title; import seedu.address.model.tag.Tag; /** @@ -20,25 +36,92 @@ */ public class ParserUtil { - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_EMPTY_CANDIDATE_INDEXES = "You must enter at least one candidate index"; + public static final String MESSAGE_EMPTY_INTERVIEW_INDEXES = "You must enter an interview index"; + public static final String MESSAGE_INVALID_INDEX = + "Index should be a non-zero unsigned integer, and less than the maximum value (2147483647).\n"; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { String trimmedIndex = oneBasedIndex.trim(); + if (trimmedIndex.equals("")) { + throw new ParseException(""); + } + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { throw new ParseException(MESSAGE_INVALID_INDEX); } return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses a string of keywords as List delimited by space. + * + * @param keywords Input String. + * @return List of keywords. + */ + public static List<String> parseKeywords(String keywords) { + requireNonNull(keywords); + String trimmedKeywords = keywords.trim(); + + List<String> output = new ArrayList<String>(Arrays.asList(trimmedKeywords.split("\\s+"))); + + output.removeAll(Arrays.asList("")); + + return output; + } + + /** + * Parses a string of time keyword delimited by spaces. + * + * @param keywords Input String. + * @return List of LocalTimes. + * @throws ParseException Input string is not valid time format HHMM. + */ + public static List<LocalTime> parseTimeKeywords(String keywords) throws ParseException { + requireNonNull(keywords); + String trimmedKeywords = keywords.trim(); + + List<String> timeStrings = new ArrayList<String>(Arrays.asList(trimmedKeywords.split("\\s+"))); + + timeStrings.removeAll(Arrays.asList("")); + + List<LocalTime> output = new ArrayList<>(); + + String timeFormat = "^[0-9]{4}$"; + + + for (String time : timeStrings) { + Pattern p = Pattern.compile(timeFormat); + Matcher m = p.matcher(time); + if (m.find()) { + int hour = Integer.parseInt(time.substring(0, 2)); + int min = Integer.parseInt(time.substring(2)); + try { + output.add(LocalTime.of(hour, min)); + } catch (DateTimeException e) { + throw new ParseException(Interview.MESSAGE_TIME_CONSTRAINTS); + } + } else { + throw new ParseException(Interview.MESSAGE_TIME_CONSTRAINTS); + } + + } + + return output; + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. * + * @param name Input String. + * @return Name of a candidate. * @throws ParseException if the given {@code name} is invalid. */ public static Name parseName(String name) throws ParseException { @@ -50,10 +133,29 @@ public static Name parseName(String name) throws ParseException { return new Name(trimmedName); } + /** + * Parses a {@code String tag} into a {@code Tag}. + * Leading and trailing whitespaces will be trimmed. + * + * @param tag Input String. + * @return Tag of a candidate. + * @throws ParseException if the given {@code tag} is invalid. + */ + public static Tag parseTag(String tag) throws ParseException { + requireNonNull(tag); + String trimmedTag = tag.trim(); + if (!Tag.isValidTagName(trimmedTag)) { + throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + } + return new Tag(trimmedTag); + } + /** * Parses a {@code String phone} into a {@code Phone}. * Leading and trailing whitespaces will be trimmed. * + * @param phone Input String. + * @return Phone Number of a candidate. * @throws ParseException if the given {@code phone} is invalid. */ public static Phone parsePhone(String phone) throws ParseException { @@ -69,6 +171,8 @@ public static Phone parsePhone(String phone) throws ParseException { * Parses a {@code String address} into an {@code Address}. * Leading and trailing whitespaces will be trimmed. * + * @param address Input String. + * @return Address of a candidate. * @throws ParseException if the given {@code address} is invalid. */ public static Address parseAddress(String address) throws ParseException { @@ -84,6 +188,8 @@ public static Address parseAddress(String address) throws ParseException { * Parses a {@code String email} into an {@code Email}. * Leading and trailing whitespaces will be trimmed. * + * @param email Input String. + * @return Email of a candidate. * @throws ParseException if the given {@code email} is invalid. */ public static Email parseEmail(String email) throws ParseException { @@ -96,22 +202,28 @@ public static Email parseEmail(String email) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String status} into a {@code Status}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @param status Input String. + * @return Status of a candidate. + * @throws ParseException If the given {@code status} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + public static Status parseStatus(String status) throws ParseException { + String trimmedStatus = status.trim().toUpperCase(); + if (!Status.isValidStatus(trimmedStatus)) { + throw new ParseException(Status.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + + return Status.parseStatus(trimmedStatus); } /** * Parses {@code Collection<String> tags} into a {@code Set<Tag>}. + * + * @param tags Input Strings. + * @return Tags of a candidate. + * @throws ParseException If the given {@code tags} is invalid. */ public static Set<Tag> parseTags(Collection<String> tags) throws ParseException { requireNonNull(tags); @@ -121,4 +233,217 @@ public static Set<Tag> parseTags(Collection<String> tags) throws ParseException } return tagSet; } + + /** + * Parses {@code String title} into a {@code Title} + * Leading and trailing whitespaces will be trimmed. + * + * @param title Input String. + * @return Title of a Job Position. + * @throws ParseException If the given {@code title} is invalid. + */ + public static Title parseTitle(String title) throws ParseException { + requireNonNull(title); + String trimmedTitle = title.trim(); + if (!Title.isValidTitle(trimmedTitle)) { + throw new ParseException(Title.MESSAGE_CONSTRAINTS); + } + return new Title(title); + } + + /** + * Parses a {@code String position} into a {@code Position}. + * Leading and trailing whitespaces will be trimmed. + * + * @param position Input String. + * @return A Job Position. + * @throws ParseException If the given {@code position} is invalid. + */ + public static Position parsePosition(String position) throws ParseException { + requireNonNull(position); + String trimmedPosition = position.trim(); + if (!Title.isValidTitle(trimmedPosition)) { + throw new ParseException(Position.MESSAGE_CONSTRAINTS); + } + + Title title = new Title(trimmedPosition); + return new Position(title); + } + + + /** + * Parses {@code Collection<String> position} into a {@code Set<Position>}. + * + * @param positions Input Strings. + * @return A Set of Job Positions. + * @throws ParseException If the given {@code positions} is invalid. + */ + public static Set<Position> parsePositions(Collection<String> positions) throws ParseException { + requireNonNull(positions); + final Set<Position> positionSet = new HashSet<>(); + for (String positionName : positions) { + positionSet.add(parsePosition(positionName)); + } + return positionSet; + } + + /** + * Parses {@code String positionStatus} into a {@code PositionStatus}. + * + * @param positionStatus Input String. + * @return Status of a Job Position. + * @throws ParseException If the given {@code positionStatus} is invalid + */ + public static PositionStatus parsePositionStatus(String positionStatus) throws ParseException { + requireNonNull(positionStatus); + String trimmedStatus = positionStatus.trim(); + if (!PositionStatus.isValidPositionStatus(trimmedStatus)) { + throw new ParseException(PositionStatus.MESSAGE_CONSTRAINTS); + } + + // Assumes trimmedStatus is valid. trimmedStatus can only be "open" or "closed". + if (trimmedStatus.equals("open")) { + return PositionStatus.OPEN; + } else { + return PositionStatus.CLOSED; + } + } + + /** + * Parses {@code String date} into a {@code LocalDate}. + * + * @param date Input String. + * @return LocalDate of an Interview. + * @throws ParseException If the given {@code date} is invalid + */ + public static LocalDate parseDate(String date) throws ParseException { + String dateFormat = "^[0-9]{1,2}[\\\\/][0-9]{1,2}[\\\\/][0-9]{4}$"; + Pattern p = Pattern.compile(dateFormat); + Matcher m = p.matcher(date); + if (m.find()) { + String[] foundDate = m.group().split("/"); + int year = Integer.parseInt(foundDate[2]); + int month = Integer.parseInt(foundDate[1]); + int day = Integer.parseInt(foundDate[0]); + try { + return LocalDate.of(year, month, day); + } catch (DateTimeException e) { + throw new ParseException(Interview.MESSAGE_DATE_CONSTRAINTS); + } + } + throw new ParseException(Interview.MESSAGE_DATE_CONSTRAINTS); + } + + /** + * Parses {@code String time} into a {@code LocalTime}. + * + * @param time Input String. + * @return LocalTime of an Interview. + * @throws ParseException If the given {@code time} is invalid + */ + public static LocalTime parseTime(String time) throws ParseException { + String timeFormat = "^[0-9]{4}$"; + Pattern p = Pattern.compile(timeFormat); + Matcher m = p.matcher(time); + if (m.find()) { + int hour = Integer.parseInt(time.substring(0, 2)); + int min = Integer.parseInt(time.substring(2)); + try { + return LocalTime.of(hour, min); + } catch (DateTimeException e) { + throw new ParseException(Interview.MESSAGE_TIME_CONSTRAINTS); + } + } + throw new ParseException(Interview.MESSAGE_TIME_CONSTRAINTS); + } + + /** + * Parses {@code String duration} into a {@code Duration}. + * + * @param duration Input String. + * @return Duration of an Interview. + * @throws ParseException If the given {@code duration} is invalid + */ + public static Duration parseDuration(String duration) throws ParseException { + try { + long actualDuration = Long.parseLong(duration); + //capped at strictly less than 24 hours or 1440 minutes + if (actualDuration <= 0 || actualDuration >= 1440) { + throw new ParseException(Interview.MESSAGE_DURATION_CONSTRAINTS_INVALID_NUMBER); + } + return Duration.ofMinutes(actualDuration); + } catch (NumberFormatException e) { + throw new ParseException(Interview.MESSAGE_DURATION_CONSTRAINTS_NOT_A_NUMBER); + } + } + + /** + * Parses a {@code String status} into a {@code InterviewStatus} + * Leading and trailing whitespaces will be trimmed. + * + * @param status Input String. + * @return InterviewStatus of an Interview. + * @throws ParseException If the given {@code status} is invalid + */ + public static InterviewStatus parseInterviewStatus(String status) throws ParseException { + requireNonNull(status); + String trimmedStatus = status.trim().toUpperCase(); + if (!InterviewStatus.isValidInterviewStatus(trimmedStatus)) { + throw new ParseException(InterviewStatus.MESSAGE_CONSTRAINTS); + } + + // Assumes trimmedStatus is valid. trimmedStatus can only be "pending" or "completed". + if (trimmedStatus.equals("PENDING")) { + return InterviewStatus.PENDING; + } else { + return InterviewStatus.COMPLETED; + } + } + + /** + * Parses {@code Collection<String> indexes} into a {@code Set<Index>}. + * + * @param indexes Input Strings. + * @return A Set of Indexes. + * @throws ParseException If the given {@code indexes} is invalid + */ + public static Set<Index> parseIndexes(Collection<String> indexes) throws ParseException { + requireNonNull(indexes); + final Set<Index> indexSet = new HashSet<>(); + for (String index : indexes) { + indexSet.add(parseIndex(index)); + } + return indexSet; + } + + /** + * Parses {@code String indexes} into a {@code Set<Index>}. + * + * @param indexes Input Strings. + * @return A Set of Indexes for Candidates. + * @throws ParseException If the given {@code indexes} is invalid + */ + public static Set<Index> parseCandidateIndexes(String indexes) throws ParseException { + requireNonNull(indexes); + String trimmedKeywords = indexes.trim(); + Set<Index> characterIndexes = new HashSet<>(); + + List<String> temp = new ArrayList<>(Arrays.asList(trimmedKeywords.split("\\s+"))); + + temp.removeAll(Arrays.asList("")); + + List<String> tempWithoutDuplicates = temp.stream() + .distinct() + .collect(Collectors.toList()); + + for (String index : tempWithoutDuplicates) { + characterIndexes.add(parseIndex(index)); + } + + if (characterIndexes.isEmpty()) { + throw new ParseException(MESSAGE_EMPTY_CANDIDATE_INDEXES); + } + + return characterIndexes; + } } diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/address/logic/parser/Prefix.java index c859d5fa5db..4f74014ccbb 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/seedu/address/logic/parser/Prefix.java @@ -2,7 +2,7 @@ /** * A prefix that marks the beginning of an argument in an arguments string. - * E.g. 't/' in 'add James t/ friend'. + * E.g. 'tag=' in 'add James tag=friend'. */ public class Prefix { private final String prefix; diff --git a/src/main/java/seedu/address/logic/parser/RemarkCandidateCommandParser.java b/src/main/java/seedu/address/logic/parser/RemarkCandidateCommandParser.java new file mode 100644 index 00000000000..78a209ccb26 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemarkCandidateCommandParser.java @@ -0,0 +1,37 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.candidate.RemarkCandidateCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Remark; + +public class RemarkCandidateCommandParser implements Parser<RemarkCandidateCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the EditCommand + * and returns an RemarkCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemarkCandidateCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_REMARK); + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ive.getMessage()) + + RemarkCandidateCommand.MESSAGE_USAGE, ive); + } + + Remark remark = new Remark(argMultimap.getValue(PREFIX_REMARK).orElse("")); + + return new RemarkCandidateCommand(index, remark); + } +} diff --git a/src/main/java/seedu/address/logic/parser/UnassignInterviewCommandParser.java b/src/main/java/seedu/address/logic/parser/UnassignInterviewCommandParser.java new file mode 100644 index 00000000000..bd589589a5b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnassignInterviewCommandParser.java @@ -0,0 +1,57 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_INDEX; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.interview.UnassignInterviewCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class UnassignInterviewCommandParser implements Parser<UnassignInterviewCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the UnassignInterviewCommand + * and returns a UnassignInterviewCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format. + */ + public UnassignInterviewCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_CANDIDATE_INDEX, PREFIX_INTERVIEW_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_CANDIDATE_INDEX, PREFIX_INTERVIEW_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UnassignInterviewCommand.MESSAGE_USAGE)); + } + + Index interviewIndex; + try { + interviewIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INTERVIEW_INDEX).orElse("")); + } catch (ParseException pe) { + throw new ParseException(ParserUtil.MESSAGE_EMPTY_INTERVIEW_INDEXES); + } + + Set<Index> candidateIndexes; + String candidateIndexInput = argMultimap.getValue(PREFIX_CANDIDATE_INDEX).get(); + if (candidateIndexInput.equals("*")) { + return new UnassignInterviewCommand(interviewIndex, true); + } else { + candidateIndexes = ParserUtil.parseCandidateIndexes(candidateIndexInput); + return new UnassignInterviewCommand(interviewIndex, candidateIndexes); + } + } + + /** + * 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/position/AddPositionCommand.java b/src/main/java/seedu/address/logic/position/AddPositionCommand.java new file mode 100644 index 00000000000..f8b6365241b --- /dev/null +++ b/src/main/java/seedu/address/logic/position/AddPositionCommand.java @@ -0,0 +1,56 @@ +package seedu.address.logic.position; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.position.Position; + +public class AddPositionCommand extends Command { + + public static final String COMMAND_WORD = "add_p"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a position to the HR Manager.\n" + + "Parameters: " + + PREFIX_TITLE + "TITLE\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TITLE + "Accountant"; + + public static final String MESSAGE_SUCCESS = "New position added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This position already exists in the HR Manager"; + + private final Position toAdd; + + /** + * Creates an AddPositionCommand to add the specified {@code Person} + */ + public AddPositionCommand(Position position) { + requireNonNull(position); + toAdd = position; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasPosition(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.addPosition(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), CommandResult.CommandType.POSITION); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddPositionCommand // instanceof handles nulls + && toAdd.equals(((AddPositionCommand) other).toAdd)); + } + + + +} diff --git a/src/main/java/seedu/address/logic/position/DeletePositionCommand.java b/src/main/java/seedu/address/logic/position/DeletePositionCommand.java new file mode 100644 index 00000000000..b8ddd439c21 --- /dev/null +++ b/src/main/java/seedu/address/logic/position/DeletePositionCommand.java @@ -0,0 +1,55 @@ +package seedu.address.logic.position; + +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.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.position.Position; + +public class DeletePositionCommand extends Command { + public static final String COMMAND_WORD = "delete_p"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the position identified by the index number used in the displayed position list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_POSITION_SUCCESS = "Deleted Position: %1$s"; + + private final Index targetIndex; + + public DeletePositionCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Position> lastShownList = model.getFilteredPositionList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_POSITION_DISPLAYED_INDEX); + } + + + Position positionToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deletePositionFromPerson(positionToDelete); + model.deletePosition(positionToDelete); + + return new CommandResult(String.format(MESSAGE_DELETE_POSITION_SUCCESS, positionToDelete), + CommandResult.CommandType.POSITION); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeletePositionCommand // instanceof handles nulls + && targetIndex.equals(((DeletePositionCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/position/EditPositionCommand.java b/src/main/java/seedu/address/logic/position/EditPositionCommand.java new file mode 100644 index 00000000000..784502538e4 --- /dev/null +++ b/src/main/java/seedu/address/logic/position/EditPositionCommand.java @@ -0,0 +1,208 @@ +package seedu.address.logic.position; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; +import seedu.address.model.position.Position.PositionStatus; +import seedu.address.model.position.Title; + +public class EditPositionCommand extends Command { + public static final String COMMAND_WORD = "edit_p"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the position identified " + + "by the index number used in the displayed position list. " + + "Existing values will be overwritten by the input values.\n" + + "NOTE: only one field can be edited at one time.\n" + + "Valid status values: open, closed.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_TITLE + "TITLE] " + + "[" + PREFIX_POSITION_STATUS + "POSITION STATUS]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_POSITION_STATUS + "closed\n"; + + public static final String MESSAGE_EDIT_POSITION_SUCCESS = "Edited Position: %1$s"; + public static final String MESSAGE_NOT_EDITED = "One field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_POSITION = "This position already exists in the position list."; + public static final String MESSAGE_BOTH_FIELDS_EDITED = "Only one field can be edited at one time."; + + private final Index index; + private final EditPositionCommand.EditPositionDescriptor editPositionDescriptor; + + /** + * @param index of the position in the filtered position list to edit + * @param editPositionDescriptor details to edit the position with + */ + public EditPositionCommand(Index index, EditPositionCommand.EditPositionDescriptor editPositionDescriptor) { + requireNonNull(index); + requireNonNull(editPositionDescriptor); + + this.index = index; + this.editPositionDescriptor = new EditPositionCommand.EditPositionDescriptor(editPositionDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List<Position> lastShownPositionList = model.getFilteredPositionList(); + List<Person> lastShownPersonList = model.getFilteredPersonList(); + List<Interview> lastShownInterviewList = model.getFilteredInterviewList(); + + // Save updated position in the positions.json file. + if (index.getZeroBased() >= lastShownPositionList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_POSITION_DISPLAYED_INDEX); + } + + Position positionToEdit = lastShownPositionList.get(index.getZeroBased()); + Position editedPosition = createEditedPosition(positionToEdit, editPositionDescriptor); + + if (editPositionDescriptor.isBothFieldsEdited()) { + throw new CommandException(MESSAGE_BOTH_FIELDS_EDITED); + } + + if (!positionToEdit.isSamePosition(editedPosition) && model.hasPosition(editedPosition)) { + throw new CommandException(MESSAGE_DUPLICATE_POSITION); + } + + model.setPosition(positionToEdit, editedPosition); + + for (Person person : lastShownPersonList) { + Set<Position> positions = person.getPositions(); + if (positions.contains(positionToEdit)) { + person.deletePosition(positionToEdit); + + // if closing position, deletes position from candidate and + // does not add edited position back to candidate's positions + if (!editPositionDescriptor.getTitle().equals(Optional.empty()) + && !editPositionDescriptor.getPositionStatus().equals(Optional.of(PositionStatus.CLOSED))) { + person.addPosition(editedPosition); + } + } + } + + for (Interview interview : lastShownInterviewList) { + Position interviewPosition = interview.getPosition(); + if (interviewPosition.isSamePosition(positionToEdit)) { + interview.setPosition(editedPosition); + } + } + + return new CommandResult(String.format(MESSAGE_EDIT_POSITION_SUCCESS, editedPosition), + CommandResult.CommandType.POSITION); + } + + /** + * Creates and returns a {@code Position} with the details of {@code positionToEdit} + * edited with {@code editPositionDescriptor}. + */ + private static Position createEditedPosition( + Position positionToEdit, EditPositionCommand.EditPositionDescriptor editPositionDescriptor) { + assert positionToEdit != null; + + Title updatedTitle = editPositionDescriptor.getTitle().orElse(positionToEdit.getTitle()); + PositionStatus updatedStatus = editPositionDescriptor.getPositionStatus().orElse(positionToEdit.getStatus()); + + return new Position(updatedTitle, updatedStatus); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPositionCommand)) { + return false; + } + + // state check + EditPositionCommand e = (EditPositionCommand) other; + return index.equals(e.index) + && editPositionDescriptor.equals(e.editPositionDescriptor); + } + + /** + * Stores the details to edit the position with. Each non-empty field value will replace the + * corresponding field value of the position. + */ + public static class EditPositionDescriptor { + private Title title; + private PositionStatus status; + + public EditPositionDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditPositionDescriptor(EditPositionCommand.EditPositionDescriptor toCopy) { + setTitle(toCopy.title); + setPositionStatus(toCopy.status); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(title, status); + } + + /** + * Returns true if both fields are edited. + */ + public boolean isBothFieldsEdited() { + return (CollectionUtil.isAnyNonNull(title) && CollectionUtil.isAnyNonNull(status)); + } + + public void setTitle(Title title) { + this.title = title; + } + + public Optional<Title> getTitle() { + return Optional.ofNullable(title); + } + + public void setPositionStatus(PositionStatus status) { + this.status = status; + } + + public Optional<PositionStatus> getPositionStatus() { + return Optional.ofNullable(status); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPositionCommand.EditPositionDescriptor)) { + return false; + } + + // state check + EditPositionCommand.EditPositionDescriptor e = (EditPositionCommand.EditPositionDescriptor) other; + + return getTitle().equals(e.getTitle()) + && getPositionStatus().equals(e.getPositionStatus()); + } + } +} diff --git a/src/main/java/seedu/address/logic/position/FindPositionCommand.java b/src/main/java/seedu/address/logic/position/FindPositionCommand.java new file mode 100644 index 00000000000..0c1869696df --- /dev/null +++ b/src/main/java/seedu/address/logic/position/FindPositionCommand.java @@ -0,0 +1,56 @@ +package seedu.address.logic.position; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.position.FindPositionCommandPredicate; + + + +/** + * Finds and lists all persons in HR Manager whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindPositionCommand extends Command { + + public static final String COMMAND_WORD = "find_p"; + + public static final String MESSAGE_SUCCESS = "Listed all found positions"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all positions that contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: " + + "[" + PREFIX_TITLE + "TITLE]... " + + "[" + PREFIX_POSITION_STATUS + "POSITION STATUS]...\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_TITLE + "accountant"; + private final FindPositionCommandPredicate predicate; + + + + public FindPositionCommand(FindPositionCommandPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPositionList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_POSITIONS_LISTED_OVERVIEW, model.getFilteredPositionList().size()), + CommandResult.CommandType.FIND_P); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindPositionCommand // instanceof handles nulls + && predicate.equals(((FindPositionCommand) other).predicate)); // state check + } + +} diff --git a/src/main/java/seedu/address/logic/position/ListPositionCommand.java b/src/main/java/seedu/address/logic/position/ListPositionCommand.java new file mode 100644 index 00000000000..7e8a184608e --- /dev/null +++ b/src/main/java/seedu/address/logic/position/ListPositionCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.position; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_POSITIONS; + +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all positions in the HR Manager to the user. + */ +public class ListPositionCommand extends Command { + + public static final String COMMAND_WORD = "list_p"; + + public static final String MESSAGE_SUCCESS = "Listed all positions"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPositionList(PREDICATE_SHOW_ALL_POSITIONS); + return new CommandResult(MESSAGE_SUCCESS, CommandResult.CommandType.LIST_P); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 1a943a0781a..00000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List<Person> persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList<Person> getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/HrManager.java b/src/main/java/seedu/address/model/HrManager.java new file mode 100644 index 00000000000..a49ff7afaeb --- /dev/null +++ b/src/main/java/seedu/address/model/HrManager.java @@ -0,0 +1,287 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; +import seedu.address.model.interview.Interview; +import seedu.address.model.interview.UniqueInterviewList; +import seedu.address.model.person.Person; +import seedu.address.model.person.UniquePersonList; +import seedu.address.model.position.Position; +import seedu.address.model.position.UniquePositionList; + +/** + * Wraps all data at the address-book level + * Duplicates are not allowed (by .isSamePerson comparison) + */ +public class HrManager implements ReadOnlyHrManager { + + private final UniquePersonList persons; + + private final UniquePositionList positions; + + private final UniqueInterviewList interviews; + + /* + * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ + + { + persons = new UniquePersonList(); + positions = new UniquePositionList(); + interviews = new UniqueInterviewList(); + } + + public HrManager() { + } + + /** + * Creates an HrManager using the persons, positions and interviews in the {@code toBeCopied} + */ + public HrManager(ReadOnlyHrManager toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the person list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public void setPersons(List<Person> persons) { + this.persons.setPersons(persons); + } + + /** + * Replaces the contents of the position list with {@code positions}. + * {@code positions} must not contain duplicate positions. + */ + public void setPositions(List<Position> positions) { + this.positions.setPositions(positions); + } + + /** + * Replaces the contents of the interview list with {@code interviews}. + * {@code interviews} must not contain duplicate interviews. + */ + public void setInterviews(List<Interview> interviews) { + this.interviews.setInterviews(interviews); + } + + /** + * Resets the existing data of this {@code HrManager} with {@code newData}. + */ + public void resetData(ReadOnlyHrManager newData) { + requireNonNull(newData); + + setPersons(newData.getPersonList()); + setPositions(newData.getPositionList()); + setInterviews(newData.getInterviewList()); + } + + //// person-level operations + + /** + * Returns true if a person with the same identity as {@code person} exists in the HR Manager. + */ + public boolean hasPerson(Person person) { + requireNonNull(person); + return persons.contains(person); + } + + /** + * Adds a person to the HR Manager. + * The person must not already exist in the HR Manager. + */ + public void addPerson(Person p) { + persons.add(p); + } + + /** + * Replaces the given person {@code target} in the list with {@code editedPerson}. + * {@code target} must exist in the HR Manager. + * The person identity of {@code editedPerson} must not be the same as another existing person in the HR Manager. + */ + public void setPerson(Person target, Person editedPerson) { + requireNonNull(editedPerson); + + persons.setPerson(target, editedPerson); + } + + /** + * Removes {@code key} from this {@code HrManager}. + * {@code key} must exist in the HR Manager. + */ + public void removePerson(Person key) { + persons.remove(key); + } + + //// position-level operations + + /** + * Returns true if a position with the same identity as {@code position} exists in the HR Manager. + */ + public boolean hasPosition(Position position) { + requireNonNull(position); + return positions.contains(position); + } + + public Position getPosition(Position position) { + return positions.getPosition(position); + } + + /** + * Adds a position to the HR Manager. + * The position must not already exist in the HR Manager. + */ + public void addPosition(Position p) { + positions.add(p); + } + + /** + * Replaces the given position {@code target} in the list with {@code editedPosition}. + * {@code target} must exist in the HR Manager. + * The position identity of {@code editedPosition} must not be the same as another existing position in the HR + * Manager. + */ + public void setPosition(Position target, Position editedPosition) { + requireNonNull(editedPosition); + + positions.setPosition(target, editedPosition); + } + + /** + * Removes {@code key} from this {@code HrManager}. + * {@code key} must exist in the HR Manager. + */ + public void removePosition(Position key) { + positions.remove(key); + } + + /** + * Deletes a position from every candidate + * + * @param p The position to be deleted + */ + public void deletePositionFromPerson(Position p) { + for (Person person : persons) { + if (person.appliedForPosition(p)) { + person.deletePosition(p); + } + } + } + + public boolean isPositionClosed(Position toCheck) { + return positions.positionIsClosed(toCheck); + } + + //// interview-level operations + + /** + * Returns true if an interview with the same identity as {@code interview} exists in the HR Manager. + */ + public boolean hasInterview(Interview interview) { + requireNonNull(interview); + return interviews.contains(interview); + } + + /** + * Adds an interview to the HR Manager. + * The interview must not already exist in the HR Manager. + */ + public void addInterview(Interview interview) { + interviews.add(interview); + } + + /** + * Replaces the given interview {@code target} in the list with {@code editedInterview}. + * {@code target} must exist in the HR Manager. + * The interview identity of {@code editedInterview} must not be the same as another existing interview in the + * HR Manager. + */ + public void setInterview(Interview target, Interview editedInterview) { + requireNonNull(editedInterview); + + interviews.setInterview(target, editedInterview); + } + + /** + * Removes {@code key} from this {@code HrManager}. + * {@code key} must exist in the HR Manager. + */ + public void removeInterview(Interview key) { + interviews.remove(key); + } + + /** + * Deletes Interview from a Person's interviewList. + */ + public void deleteInterviewFromPerson(Interview interview) { + for (Person person : persons) { + if (person.hasInterview(interview)) { + person.deleteInterview(interview); + } + } + } + + /** + * Deletes Person from an Interview's personList. + */ + public void deletePersonFromInterview(Person person) { + for (Interview interview : interviews) { + if (interview.hasCandidate(person)) { + interview.deleteCandidate(person); + } + } + } + + //// util methods + + @Override + public String toString() { + return persons.asUnmodifiableObservableList().size() + " persons\n" + + positions.asUnmodifiableObservableList().size() + " positions\n" + + interviews.asUnmodifiableObservableList().size() + " interviews"; + } + + @Override + public ObservableList<Person> getPersonList() { + return persons.asUnmodifiableObservableList(); + } + + @Override + public ObservableList<Position> getPositionList() { + return positions.asUnmodifiableObservableList(); + } + + @Override + public ObservableList<Interview> getInterviewList() { + return interviews.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof HrManager // instanceof handles nulls + && persons.equals(((HrManager) other).persons)) + && positions.equals(((HrManager) other).positions) + && interviews.equals(((HrManager) other).interviews); + } + + @Override + public int hashCode() { + return persons.hashCode() ^ positions.hashCode() ^ interviews.hashCode(); + } + + public Person getPerson(Index index) { + return persons.getPerson(index); + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..0fbb2eff358 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -5,15 +5,30 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.index.Index; +import seedu.address.model.interview.Interview; import seedu.address.model.person.Person; +import seedu.address.model.position.Position; /** * The API of the Model component. */ public interface Model { - /** {@code Predicate} that always evaluate to true */ + /** + * {@code Predicate} that always evaluate to true + */ Predicate<Person> PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** + * {@code Predicate} that always evaluate to true + */ + Predicate<Position> PREDICATE_SHOW_ALL_POSITIONS = unused -> true; + + /** + * {@code Interview} that always evaluate to true + */ + Predicate<Interview> PREDICATE_SHOW_ALL_INTERVIEWS = unused -> true; + /** * Replaces user prefs data with the data in {@code userPrefs}. */ @@ -35,53 +50,173 @@ public interface Model { void setGuiSettings(GuiSettings guiSettings); /** - * Returns the user prefs' address book file path. + * Returns the user prefs' HR Manager Candidate file path. */ - Path getAddressBookFilePath(); + Path getHrManagerCandidatesFilePath(); /** - * Sets the user prefs' address book file path. + * Returns the user prefs' HR Manager Position file path. */ - void setAddressBookFilePath(Path addressBookFilePath); + Path getHrManagerPositionsFilePath(); /** - * Replaces address book data with the data in {@code addressBook}. + * Returns the user prefs' HR Manager Interview file path. */ - void setAddressBook(ReadOnlyAddressBook addressBook); + Path getHrManagerInterviewsFilePath(); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + /** + * Sets the user prefs' HR Manager candidate file path. + */ + void setHrManagerCandidatesFilePath(Path hrManagerCandidatesFilePath); + + /** + * Sets the user prefs' HR Manager position file path. + */ + void setHrManagerPositionsFilePath(Path hrManagerPositionsFilePath); + + /** + * Sets the user prefs' HR Manager interview file path. + */ + void setHrManagerInterviewsFilePath(Path hrManagerInterviewsFilePath); + + /** + * Replaces HR Manager data with the data in {@code addressBook}. + */ + void setHrManager(ReadOnlyHrManager addressBook); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns the HrManager + */ + ReadOnlyHrManager getHrManager(); + + /** + * Returns true if a person with the same identity as {@code person} exists in the HR Manager. */ boolean hasPerson(Person person); /** * Deletes the given person. - * The person must exist in the address book. + * The person must exist in the HR Manager. */ void deletePerson(Person target); /** * Adds the given person. - * {@code person} must not already exist in the address book. + * {@code person} must not already exist in the HR Manager. */ void addPerson(Person person); /** * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * {@code target} must exist in the HR Manager. + * The person identity of {@code editedPerson} must not be the same as another existing person in the HR Manager. */ void setPerson(Person target, Person editedPerson); - /** Returns an unmodifiable view of the filtered person list */ + /** + * Returns an unmodifiable view of the filtered person list + */ ObservableList<Person> getFilteredPersonList(); /** * 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<Person> predicate); + + /** + * Returns true if a position with the same identity as {@code position} exists in the HR Manager. + */ + boolean hasPosition(Position position); + + /** + * Returns the position stored in HR Manager {@code position} + */ + Position getPositionReference(Position position); + + /** + * Deletes the given position. + * The position must exist in the HR Manager. + */ + void deletePosition(Position target); + + /** + * Adds the given position. + * {@code position} must not already exist in the HR Manager. + */ + void addPosition(Position position); + + /** + * Replaces the given position {@code target} with {@code editedPosition}. + * {@code target} must exist in the HR Manager. + * The position identity of {@code editedPosition} must not be the same as another existing position in the HR + * Manager. + */ + void setPosition(Position target, Position editedPosition); + + /** + * Returns an unmodifiable view of the filtered position list + */ + ObservableList<Position> getFilteredPositionList(); + + /** + * Updates the filter of the filtered position list to filter by the given {@code predicate}. + * + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredPositionList(Predicate<Position> predicate); + + void deletePositionFromPerson(Position p); + + Person getPerson(Index index); + + boolean isPositionClosed(Position toCheck); + + /** + * Returns true if an interview with the same identity as {@code interview} exists in the HR Manager. + */ + boolean hasInterview(Interview interview); + + /** + * Deletes the given interview. + * The interview must exist in the HR Manager. + */ + void deleteInterview(Interview target); + + /** + * Adds the given interview. + * {@code interview} must not already exist in the HR Manager. + */ + void addInterview(Interview interview); + + /** + * Replaces the given interview {@code target} with {@code editedInterview}. + * {@code target} must exist in the HR Manager. + * The interview identity of {@code editedInterview} must not be the same as another existing interview in the + * HR Manager. + */ + void setInterview(Interview target, Interview editedInterview); + + /** + * Returns an unmodifiable view of the filtered interview list + */ + ObservableList<Interview> getFilteredInterviewList(); + + /** + * Updates the filter of the filtered interview list to filter by the given {@code predicate}. + * + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredInterviewList(Predicate<Interview> predicate); + + /** + * Deletes Interview from a Person's interviewList. + */ + void deleteInterviewFromPerson(Interview interview); + + /** + * Deletes Person from an Interview's personList. + */ + void deletePersonFromInterview(Person person); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 0650c954f5c..37f94d60b95 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,34 +11,41 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.model.interview.Interview; import seedu.address.model.person.Person; +import seedu.address.model.position.Position; /** - * Represents the in-memory model of the address book data. + * Represents the in-memory model of the HR Manager data. */ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final AddressBook addressBook; + private final HrManager hrManager; private final UserPrefs userPrefs; private final FilteredList<Person> filteredPersons; + private final FilteredList<Position> filteredPositions; + private final FilteredList<Interview> filteredInterviews; /** * Initializes a ModelManager with the given addressBook and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { + public ModelManager(ReadOnlyHrManager hrManager, ReadOnlyUserPrefs userPrefs) { super(); - requireAllNonNull(addressBook, userPrefs); + requireAllNonNull(hrManager, userPrefs); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + logger.fine("Initializing with HR Manager: " + hrManager + " and user prefs " + userPrefs); - this.addressBook = new AddressBook(addressBook); + this.hrManager = new HrManager(hrManager); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredPersons = new FilteredList<>(this.hrManager.getPersonList()); + filteredPositions = new FilteredList<>(this.hrManager.getPositionList()); + filteredInterviews = new FilteredList<>(this.hrManager.getInterviewList()); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new HrManager(), new UserPrefs()); } //=========== UserPrefs ================================================================================== @@ -66,69 +73,192 @@ public void setGuiSettings(GuiSettings guiSettings) { } @Override - public Path getAddressBookFilePath() { - return userPrefs.getAddressBookFilePath(); + public Path getHrManagerCandidatesFilePath() { + return userPrefs.getHrManagerCandidatesFilePath(); } @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); + public Path getHrManagerPositionsFilePath() { + return userPrefs.getHrManagerPositionsFilePath(); } - //=========== AddressBook ================================================================================ + @Override + public Path getHrManagerInterviewsFilePath() { + return userPrefs.getHrManagerInterviewsFilePath(); + } + + @Override + public void setHrManagerCandidatesFilePath(Path hrManagerCandidatesFilePath) { + requireNonNull(hrManagerCandidatesFilePath); + userPrefs.setHrManagerCandidatesFilePath(hrManagerCandidatesFilePath); + } @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); + public void setHrManagerPositionsFilePath(Path hrManagerPositionsFilePath) { + requireNonNull(hrManagerPositionsFilePath); + userPrefs.setHrManagerPositionsFilePath(hrManagerPositionsFilePath); } @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; + public void setHrManagerInterviewsFilePath(Path hrManagerInterviewsFilePath) { + requireNonNull(hrManagerInterviewsFilePath); + userPrefs.setHrManagerInterviewsFilePath(hrManagerInterviewsFilePath); + } + + //=========== HrManager ================================================================================ + + @Override + public void setHrManager(ReadOnlyHrManager hrManager) { + this.hrManager.resetData(hrManager); + } + + @Override + public ReadOnlyHrManager getHrManager() { + return hrManager; } @Override public boolean hasPerson(Person person) { requireNonNull(person); - return addressBook.hasPerson(person); + return hrManager.hasPerson(person); + } + + @Override + public Person getPerson(Index index) { + return hrManager.getPerson(index); } @Override public void deletePerson(Person target) { - addressBook.removePerson(target); + hrManager.removePerson(target); } @Override public void addPerson(Person person) { - addressBook.addPerson(person); + hrManager.addPerson(person); updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); } @Override public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); + hrManager.setPerson(target, editedPerson); + } + + @Override + public boolean hasPosition(Position position) { + requireNonNull(position); + return hrManager.hasPosition(position); + } + + @Override + public Position getPositionReference(Position position) { + return hrManager.getPosition(position); + } - addressBook.setPerson(target, editedPerson); + @Override + public void deletePosition(Position target) { + hrManager.removePosition(target); + } + + @Override + public void addPosition(Position position) { + hrManager.addPosition(position); + updateFilteredPositionList(PREDICATE_SHOW_ALL_POSITIONS); + } + + @Override + public void setPosition(Position target, Position editedPosition) { + requireAllNonNull(target, editedPosition); + hrManager.setPosition(target, editedPosition); + } + + public void deletePositionFromPerson(Position p) { + hrManager.deletePositionFromPerson(p); + } + + public boolean isPositionClosed(Position toCheck) { + return hrManager.isPositionClosed(toCheck); + } + + @Override + public boolean hasInterview(Interview interview) { + requireNonNull(interview); + return hrManager.hasInterview(interview); + } + + @Override + public void deleteInterview(Interview target) { + hrManager.removeInterview(target); + } + + @Override + public void addInterview(Interview interview) { + hrManager.addInterview(interview); + updateFilteredInterviewList(PREDICATE_SHOW_ALL_INTERVIEWS); + } + + @Override + public void setInterview(Interview target, Interview editedInterview) { + requireAllNonNull(target, editedInterview); + hrManager.setInterview(target, editedInterview); + } + + public void deleteInterviewFromPerson(Interview interview) { + hrManager.deleteInterviewFromPerson(interview); + } + + public void deletePersonFromInterview(Person person) { + hrManager.deletePersonFromInterview(person); } //=========== Filtered Person List Accessors ============================================================= /** * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} + * {@code versionedHrManager} */ @Override public ObservableList<Person> getFilteredPersonList() { return filteredPersons; } + /** + * Returns an unmodifiable view of the list of {@code Position} backed by the internal list of + * {@code versionedHrManager} + */ + @Override + public ObservableList<Position> getFilteredPositionList() { + return filteredPositions; + } + + /** + * Returns an unmodifiable view of the list of {@code Interview} backed by the internal list of + * {@code versionedHrManager} + */ + @Override + public ObservableList<Interview> getFilteredInterviewList() { + return filteredInterviews; + } + @Override public void updateFilteredPersonList(Predicate<Person> predicate) { requireNonNull(predicate); filteredPersons.setPredicate(predicate); } + @Override + public void updateFilteredPositionList(Predicate<Position> predicate) { + requireNonNull(predicate); + filteredPositions.setPredicate(predicate); + } + + @Override + public void updateFilteredInterviewList(Predicate<Interview> predicate) { + requireNonNull(predicate); + filteredInterviews.setPredicate(predicate); + } + @Override public boolean equals(Object obj) { // short circuit if same object @@ -143,9 +273,11 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) + return hrManager.equals(other.hrManager) && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); + && filteredPersons.equals(other.filteredPersons) + && filteredPositions.equals(other.filteredPositions) + && filteredInterviews.equals(other.filteredInterviews); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a29..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList<Person> getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyHrManager.java b/src/main/java/seedu/address/model/ReadOnlyHrManager.java new file mode 100644 index 00000000000..e73f9b80495 --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyHrManager.java @@ -0,0 +1,31 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; + +/** + * Unmodifiable view of a HR Manager + */ +public interface ReadOnlyHrManager { + + /** + * Returns an unmodifiable view of the persons list. + * This list will not contain any duplicate persons. + */ + ObservableList<Person> getPersonList(); + + /** + * Returns an unmodifiable view of the position list. + * This list will not contain any duplicate positions. + */ + ObservableList<Position> getPositionList(); + + /** + * Returns an unmodifiable view of the interview list. + * This list will not contain any duplicate interviews. + */ + ObservableList<Interview> getInterviewList(); + +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java index befd58a4c73..985bb1e1b8a 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java @@ -11,6 +11,9 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); + Path getHrManagerCandidatesFilePath(); + Path getHrManagerPositionsFilePath(); + + Path getHrManagerInterviewsFilePath(); } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 25a5fd6eab9..af8115e26cd 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,7 +14,9 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path hrManagerCandidatesFilePath = Paths.get("data" , "candidates.json"); + private Path hrManagerPositionsFilePath = Paths.get("data" , "positions.json"); + private Path hrManagerInterviewsFilePath = Paths.get("data" , "interviews.json"); /** * Creates a {@code UserPrefs} with default values. @@ -35,7 +37,9 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setHrManagerCandidatesFilePath(newUserPrefs.getHrManagerCandidatesFilePath()); + setHrManagerPositionsFilePath(newUserPrefs.getHrManagerPositionsFilePath()); + setHrManagerInterviewsFilePath(newUserPrefs.getHrManagerInterviewsFilePath()); } public GuiSettings getGuiSettings() { @@ -47,15 +51,34 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getHrManagerCandidatesFilePath() { + return hrManagerCandidatesFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; + public Path getHrManagerPositionsFilePath() { + return hrManagerPositionsFilePath; } + public Path getHrManagerInterviewsFilePath() { + return hrManagerInterviewsFilePath; + } + + public void setHrManagerCandidatesFilePath(Path hrManagerCandidatesFilePath) { + requireNonNull(hrManagerCandidatesFilePath); + this.hrManagerCandidatesFilePath = hrManagerCandidatesFilePath; + } + + public void setHrManagerPositionsFilePath(Path hrManagerPositionsFilePath) { + requireNonNull(hrManagerPositionsFilePath); + this.hrManagerPositionsFilePath = hrManagerPositionsFilePath; + } + + public void setHrManagerInterviewsFilePath(Path hrManagerInterviewsFilePath) { + requireNonNull(hrManagerInterviewsFilePath); + this.hrManagerInterviewsFilePath = hrManagerInterviewsFilePath; + } + + @Override public boolean equals(Object other) { if (other == this) { @@ -68,20 +91,23 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return guiSettings.equals(o.guiSettings) - && addressBookFilePath.equals(o.addressBookFilePath); + && hrManagerCandidatesFilePath.equals(o.hrManagerCandidatesFilePath) + && hrManagerPositionsFilePath.equals(o.hrManagerPositionsFilePath) + && hrManagerInterviewsFilePath.equals(o.hrManagerInterviewsFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, hrManagerCandidatesFilePath, hrManagerPositionsFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal candidates data file location : " + hrManagerCandidatesFilePath); + sb.append("\nLocal positions data file location : " + hrManagerPositionsFilePath); + sb.append("\nLocal interviews data file location : " + hrManagerInterviewsFilePath); return sb.toString(); } - } diff --git a/src/main/java/seedu/address/model/interview/FindInterviewCommandPredicate.java b/src/main/java/seedu/address/model/interview/FindInterviewCommandPredicate.java new file mode 100644 index 00000000000..dd289a313c9 --- /dev/null +++ b/src/main/java/seedu/address/model/interview/FindInterviewCommandPredicate.java @@ -0,0 +1,109 @@ +package seedu.address.model.interview; + +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class FindInterviewCommandPredicate implements Predicate<Interview> { + + private List<String> candidateKeywords = new ArrayList<String>(); + private List<String> dateKeywords = new ArrayList<String>(); + private List<String> interviewStatusKeywords = new ArrayList<String>(); + private List<String> positionKeywords = new ArrayList<String>(); + private List<LocalTime> timeKeywords = new ArrayList<LocalTime>(); + + public FindInterviewCommandPredicate() {} + + /** + * Alternative Constructor with keyword field. + * @param candidateKeywords + * @param dateKeywords + * @param interviewStatusKeywords + * @param positionKeywords + * @param timeKeywords + */ + public FindInterviewCommandPredicate(List<String> candidateKeywords, List<String> dateKeywords, + List<String> interviewStatusKeywords, List<String> positionKeywords, + List<LocalTime> timeKeywords) { + this.candidateKeywords = candidateKeywords; + this.dateKeywords = dateKeywords; + this.interviewStatusKeywords = interviewStatusKeywords; + this.positionKeywords = positionKeywords; + this.timeKeywords = timeKeywords; + } + + @Override + public boolean test(Interview interview) { + boolean candidateCheck = candidateKeywords.isEmpty() || candidateKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(interview.getCandidateNameString(), keyword)); + + boolean dateCheck = dateKeywords.isEmpty() || dateKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(interview.getDateInFormattedString(), keyword)); + + boolean interviewStatusCheck = interviewStatusKeywords.isEmpty() || interviewStatusKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(interview.getStatusInString(), keyword)); + + boolean positionCheck = positionKeywords.isEmpty() || positionKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase( + interview.getPositionTitle().fullTitle, keyword)); + + + boolean timeCheck = timeKeywords.isEmpty() || timeKeywords.stream() + .anyMatch(time -> (interview.getStartTime().isBefore(time) || interview.getStartTime().equals(time)) + && (interview.getEndTime().isAfter(time) || interview.getEndTime().equals(time))); + + + return candidateCheck && dateCheck && interviewStatusCheck && positionCheck && timeCheck; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindInterviewCommandPredicate // instanceof handles nulls + && candidateKeywords.equals(((FindInterviewCommandPredicate) other).candidateKeywords) // state check + && dateKeywords.equals(((FindInterviewCommandPredicate) other).dateKeywords) + && interviewStatusKeywords.equals(((FindInterviewCommandPredicate) other).interviewStatusKeywords) + && positionKeywords.equals(((FindInterviewCommandPredicate) other).positionKeywords) + && timeKeywords.equals(((FindInterviewCommandPredicate) other).timeKeywords)); + + } + + public void setCandidateKeywords(List<String> candidateKeywords) { + this.candidateKeywords = candidateKeywords; + } + + public void setDateKeywords(List<String> dateKeywords) { + this.dateKeywords = dateKeywords; + } + + public void setPositionKeywords(List<String> positionKeywords) { + this.positionKeywords = positionKeywords; + } + + public void setTimeKeywords(List<LocalTime> timeKeywords) { + this.timeKeywords = timeKeywords; + } + + public void setInterviewStatusKeywords(List<String> interviewStatusKeywords) { + this.interviewStatusKeywords = interviewStatusKeywords; + } + + + /** + * If any field is provided. + * @return if any field is provided. + */ + public boolean isAnyField() { + return !Stream.of(candidateKeywords, dateKeywords, interviewStatusKeywords, + positionKeywords, timeKeywords) + .allMatch(List::isEmpty); + } + +} diff --git a/src/main/java/seedu/address/model/interview/Interview.java b/src/main/java/seedu/address/model/interview/Interview.java new file mode 100644 index 00000000000..b3b13dc8abc --- /dev/null +++ b/src/main/java/seedu/address/model/interview/Interview.java @@ -0,0 +1,331 @@ +package seedu.address.model.interview; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; + +/** + * Represents an Interview in the HR Manager, with default status 'pending'. + * Guarantees: immutable; position is valid and not null. + */ +public class Interview { + + // public static final String MESSAGE_CONSTRAINTS = ""; + + public static final String MESSAGE_DATE_CONSTRAINTS = "Date should be valid and in DD/MM/YYYY format."; + public static final String MESSAGE_TIME_CONSTRAINTS = "Time should be be valid and in HHMM format."; + + public static final String MESSAGE_DURATION_CONSTRAINTS_NOT_A_NUMBER = "Duration should be a positive integer."; + public static final String MESSAGE_DURATION_CONSTRAINTS_INVALID_NUMBER = "Duration is in minutes, " + + "it should be more than 0 and less than 1440."; + + private Position position; + + private InterviewStatus status; + + private Set<Person> candidates; + + private final LocalTime startTime; + + private final LocalDate date; + + private final Duration duration; + + private final Set<Integer> candidateIDs = new HashSet<>(); + + public enum InterviewStatus { + PENDING, + COMPLETED; + + public static final String MESSAGE_CONSTRAINTS = "Interview Status can ony take the values:\n" + + "pending\n" + + "completed\n"; + + private static final List<String> validStatus = new ArrayList<>(Arrays.asList("PENDING", + "COMPLETED", "")); + + /** + * Return true if a give string is a valid interview status. + * Only "pending" and "completed" are valid strings for interview status. + * + * @param test A string to test if is a valid interview status. + * @return True if test string is a valid interview status, false otherwise. + */ + public static boolean isValidInterviewStatus(String test) { + return validStatus.contains(test); + } + + } + + /** + * Constructs a {@code Interview}. + * + * @param position A position for the interview. + * @param candidates A list of names of the candidates attending the interview. + * @param date The date of the interview. + * @param startTime The start time of the interview. + * @param duration The duration of the interview. + */ + public Interview(Position position, Set<Person> candidates, LocalDate date, + LocalTime startTime, Duration duration) { + requireAllNonNull(position, candidates, startTime, duration); + this.position = position; + this.candidates = candidates; + this.date = date; + this.startTime = startTime; + this.duration = duration; + } + + /** + * Constructs a {@code Interview}. + * + * @param position A position for the interview. + * @param candidates A list of names of the candidates attending the interview. + * @param startTime The start time of the interview. + * @param date The date of the interview. + * @param duration The duration of the interview. + * @param status The interview status. + */ + public Interview(Position position, Set<Person> candidates, LocalDate date, + LocalTime startTime, Duration duration, InterviewStatus status) { + this(position, candidates, date, startTime, duration); + if (status != null) { + this.status = status; + } + } + + /** + * Returns true if both interviews have the same position, candidates, start time, and duration. + * This defines a weaker notion of equality between two interviews. + */ + public boolean isSameInterview(Interview otherInterview) { + if (otherInterview == this) { + return true; + } + + return otherInterview != null + && otherInterview.getPositionTitle().equals(getPositionTitle()) + && otherInterview.getDate().equals(getDate()) + && otherInterview.getStartTime().equals(getStartTime()) + && otherInterview.getDuration().equals(getDuration()); + } + + public Position getPosition() { + assert this.position != null : "Interview position is non-null"; + return position; + } + + public void setPosition(Position newPosition) { + position = newPosition; + } + + public Set<Person> getCandidates() { + assert this.candidates != null : "Interview candidate names set is non-null."; + return this.candidates; + } + + public void setCandidates(Set<Person> personSet) { + assert this.candidates != null : "Interview candidate names set is non-null."; + this.candidates = new HashSet<>(); + this.candidates.addAll(personSet); + } + + public LocalDate getDate() { + assert this.date != null : "Interview date is non-null."; + return this.date; + } + + public String getDateInFormattedString() { + assert this.date != null : "Interview date is non-null."; + String[] temp = this.date.toString().split("-"); + return temp[2] + "/" + temp[1] + "/" + temp[0]; + } + + public LocalTime getStartTime() { + assert this.startTime != null : "Interview start time is non-null."; + return this.startTime; + } + + public String getTimeInFormattedString() { + assert this.startTime != null : "Interview start time is non-null."; + return this.startTime.toString().replace(":", ""); + } + + public Duration getDuration() { + assert this.duration != null : "Interview duration is non-null."; + return this.duration; + } + + public String getDurationInFormattedString() { + assert this.duration != null : "Interview duration is non-null."; + String temp = this.duration.toString().replace("PT", ""); + if (temp.contains("H") && temp.contains("M")) { + int endHourIndex = temp.indexOf("H"); + return String.valueOf(Integer.parseInt(temp.substring(0, endHourIndex)) * 60 + + Integer.parseInt(temp.substring(endHourIndex + 1, temp.length() - 1))); + } else if (temp.contains("H")) { + return String.valueOf(Integer.parseInt(temp.substring(0, temp.length() - 1)) * 60); + } else { + return temp.substring(0, temp.length() - 1); + } + } + + public Title getPositionTitle() { + assert this.position != null : "Interview position is non-null."; + return this.position.getTitle(); + } + + public InterviewStatus getStatus() { + assert this.status != null : "Interview status is non-null."; + return this.status; + } + + public String getStatusInString() { + assert this.status != null : "Interview status is non-null."; + return this.status.toString(); + } + + public void setStatus(InterviewStatus status) { + requireNonNull(status); + this.status = status; + } + + /** + * Checks if another interview has timing overlaps with this interview. + * @param other the interview to check against. + * @return true if they start at the same time or if one begins before the other ends. + */ + public boolean hasOverLapWith(Interview other) { + LocalDateTime toScheduleStart = LocalDateTime.of(other.getDate(), other.getStartTime()); + LocalDateTime toScheduleEnd = LocalDateTime.of(other.getDate(), other.getEndTime()); + + LocalDateTime start = LocalDateTime.of(getDate(), getStartTime()); + LocalDateTime end = LocalDateTime.of(getDate(), getEndTime()); + + if (getStartTime().getHour() > getEndTime().getHour() + || (getStartTime().getHour() == getEndTime().getHour() && getDuration().toHours() > 22)) { + end = LocalDateTime.of(getDate().plusDays(1), getEndTime()); + } + + if (other.getStartTime().getHour() > other.getEndTime().getHour() + || (other.getStartTime().getHour() == other.getEndTime().getHour() + && other.getDuration().toHours() > 22)) { + toScheduleEnd = LocalDateTime.of(other.getDate().plusDays(1), getEndTime()); + } + + return (toScheduleStart.isAfter(start) && toScheduleStart.isBefore(end)) + || start.isEqual(toScheduleStart) + || (start.isAfter(toScheduleStart) && start.isBefore(toScheduleEnd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Interview // instanceof handles nulls + && position.equals(((Interview) other).position) // position check + && candidates.equals(((Interview) other).candidates) // candidates check + && date.equals(((Interview) other).date) + && startTime.equals(((Interview) other).startTime) // startTime check + && duration.equals(((Interview) other).duration) + && status.equals(((Interview) other).status)); // status check + } + + public void setCandidateIDs(Set<Integer> candidateIDs) { + this.candidateIDs.addAll(candidateIDs); + } + + public Set<Integer> getCandidateIDs() { + return candidateIDs; + } + + //for checking and adding interview to person + public boolean hasCandidate(Person person) { + return candidates.contains(person); + } + + public void deleteCandidate(Person person) { + candidates.remove(person); + } + + public void addCandidate(Person person) { + candidates.add(person); + } + + @Override + public int hashCode() { + return Objects.hash(position, date, startTime, duration, status); + } + + /** + * Format state as text for viewing. + */ + @Override + public String toString() { + return "[" + getPositionTitle().toString() + " " + + getCandidates().toString() + " " + + getDate() + " " + + getDisplayTimePeriod() + " " + + getStatusInString() + "]"; + } + + + public LocalTime getEndTime() { + return startTime.plusMinutes(duration.toMinutes()); + } + + public String getDisplayDate() { + String month = Month.of(date.getMonthValue()).toString(); + month = month.charAt(0) + month.substring(1, 3).toLowerCase(); + return date.getDayOfMonth() + " " + month + " " + date.getYear(); + } + + public String getCandidatesNames() { + Set<String> names = candidates.stream().map(c -> c.getName().fullName).collect(Collectors.toSet()); + return names.toString(); + } + + public String getCandidateNameString() { + assert this.candidates != null; + Set<String> names = candidates.stream().map(c -> c.getName().fullName).collect(Collectors.toSet()); + return names.toString().replace("[", "").replace("]", " ") + .replace(",", ""); + } + + + public String getDisplayString() { + return "[" + getPositionTitle().toString() + " " + + getCandidatesNames() + " " + + getDisplayDate() + " " + + getStartTime() + " - " + + getEndTime() + " " + + getStatusInString() + "]"; + } + + public String getDisplayTimePeriod() { + return getStartTime() + " ~ " + getEndTime(); + } + + public String getDisplayStringWithoutNames() { + return "[" + getPositionTitle().toString() + " " + + getDisplayDate() + " " + + getDisplayTimePeriod() + " " + + getStatusInString() + "]"; + } +} + diff --git a/src/main/java/seedu/address/model/interview/PositionTitleContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/interview/PositionTitleContainsKeywordsPredicate.java new file mode 100644 index 00000000000..0fd7ca733c4 --- /dev/null +++ b/src/main/java/seedu/address/model/interview/PositionTitleContainsKeywordsPredicate.java @@ -0,0 +1,35 @@ +package seedu.address.model.interview; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Position}'s {@code Title} of a {@code Interview} matches any of the keywords given. + */ +public class PositionTitleContainsKeywordsPredicate implements Predicate<Interview> { + private final List<String> keywords; + + public PositionTitleContainsKeywordsPredicate(List<String> keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Interview interview) { + return keywords.stream() + .anyMatch(keyword -> + StringUtil.containsWordIgnoreCase(interview.getPositionTitle().fullTitle, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + // instanceof handles nulls + || (other instanceof PositionTitleContainsKeywordsPredicate + // keywords check + && keywords.equals(((PositionTitleContainsKeywordsPredicate) other).keywords)); + } + +} + diff --git a/src/main/java/seedu/address/model/interview/Tuple.java b/src/main/java/seedu/address/model/interview/Tuple.java new file mode 100644 index 00000000000..c066302b8a4 --- /dev/null +++ b/src/main/java/seedu/address/model/interview/Tuple.java @@ -0,0 +1,41 @@ +package seedu.address.model.interview; + +public class Tuple<S, T> { + private S first; + private T second; + + /** + * Public constructor for Tuple objects, that consists of a first object of type S + * and a second object of type T. + * @param first The first object of the Tuple. + * @param second The second object of the Tuple. + */ + public Tuple(S first, T second) { + this.first = first; + this.second = second; + } + + public S getFirst() { + return this.first; + } + + public T getSecond() { + return this.second; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Tuple)) { + return false; + } + + @SuppressWarnings("unchecked") + Tuple<S, T> otherTuple = (Tuple<S, T>) other; + return otherTuple.getFirst().equals(getFirst()) + && otherTuple.getSecond().equals(getSecond()); + } +} diff --git a/src/main/java/seedu/address/model/interview/UniqueInterviewList.java b/src/main/java/seedu/address/model/interview/UniqueInterviewList.java new file mode 100644 index 00000000000..729cf2e0d52 --- /dev/null +++ b/src/main/java/seedu/address/model/interview/UniqueInterviewList.java @@ -0,0 +1,139 @@ +package seedu.address.model.interview; + +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.interview.exceptions.DuplicateInterviewException; +import seedu.address.model.interview.exceptions.InterviewNotFoundException; + +/** + * A list of interviews that enforces uniqueness between its elements and does not allow nulls. + * An interview is considered unique by comparing using {@code Interview#isSameInterview(Interview)}. As such, adding + * and updating of interviews uses {@code Interview#isSameInterview(Interview)} for equality so as to ensure that the + * interview being added or updated is unique in terms of identity in the UniqueInterviewList. However, the removal of + * an interview uses Interview#equals(Object) so as to ensure that the interview with exactly the same fields will be + * removed. + * <p> + * Supports a minimal set of list operations. + * + * @see Interview#isSameInterview(Interview) + */ +public class UniqueInterviewList implements Iterable<Interview> { + + private final ObservableList<Interview> internalList = FXCollections.observableArrayList(); + private final ObservableList<Interview> internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent interview as the given argument. + */ + public boolean contains(Interview toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameInterview); + } + + /** + * Adds an interview to the list. + * The interview must not already exist in the list. + */ + public void add(Interview toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateInterviewException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the interview {@code target} in the list with {@code editedInterview}. + * {@code target} must exist in the list. + * The interview identity of {@code editedInterview} must not be the same as another existing interview in the list. + */ + public void setInterview(Interview target, Interview editedInterview) { + requireAllNonNull(target, editedInterview); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new InterviewNotFoundException(); + } + + if (!target.isSameInterview(editedInterview) && contains(editedInterview)) { + throw new DuplicateInterviewException(); + } + + internalList.set(index, editedInterview); + } + + /** + * Removes the equivalent interview from the list. + * The interview must exist in the list. + */ + public void remove(Interview toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new InterviewNotFoundException(); + } + } + + public void setInterviews(UniqueInterviewList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code interviews}. + * {@code interviews} must not contain duplicate interviews. + */ + public void setInterviews(List<Interview> interviews) { + requireAllNonNull(interviews); + if (!interviewsAreUnique(interviews)) { + throw new DuplicateInterviewException(); + } + + internalList.setAll(interviews); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<Interview> asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator<Interview> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueInterviewList // instanceof handles nulls + && internalList.equals(((UniqueInterviewList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code interviews} contains only unique interviews. + */ + private boolean interviewsAreUnique(List<Interview> interviews) { + for (int i = 0; i < interviews.size() - 1; i++) { + for (int j = i + 1; j < interviews.size(); j++) { + if (interviews.get(i).isSameInterview(interviews.get(j))) { + return false; + } + } + } + return true; + } +} + diff --git a/src/main/java/seedu/address/model/interview/exceptions/DuplicateInterviewException.java b/src/main/java/seedu/address/model/interview/exceptions/DuplicateInterviewException.java new file mode 100644 index 00000000000..a554c939972 --- /dev/null +++ b/src/main/java/seedu/address/model/interview/exceptions/DuplicateInterviewException.java @@ -0,0 +1,11 @@ +package seedu.address.model.interview.exceptions; + +/** + * Signals that the operation will result in duplicate Interviews (Interviews are considered duplicates if they have + * the same identity). + */ +public class DuplicateInterviewException extends RuntimeException { + public DuplicateInterviewException() { + super("Operation would result in duplicate interviews"); + } +} diff --git a/src/main/java/seedu/address/model/interview/exceptions/InterviewNotFoundException.java b/src/main/java/seedu/address/model/interview/exceptions/InterviewNotFoundException.java new file mode 100644 index 00000000000..b6f6a5e9287 --- /dev/null +++ b/src/main/java/seedu/address/model/interview/exceptions/InterviewNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.interview.exceptions; + +/** + * Signals that the operation is unable to find the specified interview. + */ +public class InterviewNotFoundException extends RuntimeException { } diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 60472ca22a0..96f82e53a03 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -4,7 +4,7 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's address in the address book. + * Represents a Person's address in the HR Manager. * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} */ public class Address { diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index f866e7133de..492bb1565af 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -4,7 +4,7 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Person's email in the HR Manager. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { diff --git a/src/main/java/seedu/address/model/person/FindCandidateCommandPredicate.java b/src/main/java/seedu/address/model/person/FindCandidateCommandPredicate.java new file mode 100644 index 00000000000..fd09c361857 --- /dev/null +++ b/src/main/java/seedu/address/model/person/FindCandidateCommandPredicate.java @@ -0,0 +1,133 @@ +package seedu.address.model.person; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class FindCandidateCommandPredicate implements Predicate<Person> { + + private List<String> nameKeywords = new ArrayList<String>(); + private List<String> phoneKeywords = new ArrayList<String>(); + private List<String> emailKeywords = new ArrayList<String>(); + private List<String> addressKeywords = new ArrayList<String>(); + private List<String> tagKeywords = new ArrayList<String>(); + private List<String> statusKeywords = new ArrayList<String>(); + private List<String> positionKeywords = new ArrayList<String>(); + + public FindCandidateCommandPredicate() { + } + + /** + * Alternative Constructor with all fields. + * @param nameKeywords + * @param phoneKeywords + * @param emailKeywords + * @param addressKeywords + * @param tagKeywords + * @param statusKeywords + * @param positionKeywords + */ + public FindCandidateCommandPredicate(List<String> nameKeywords, List<String> phoneKeywords, + List<String> emailKeywords, List<String> addressKeywords, + List<String> tagKeywords, List<String> statusKeywords, + List<String> positionKeywords) { + this.nameKeywords = nameKeywords; + this.phoneKeywords = phoneKeywords; + this.emailKeywords = emailKeywords; + this.addressKeywords = addressKeywords; + this.tagKeywords = tagKeywords; + this.statusKeywords = statusKeywords; + this.positionKeywords = positionKeywords; + } + + /** + * Alternative Constructor with only keyword field. + * @param nameKeywords + */ + public FindCandidateCommandPredicate(List<String> nameKeywords) { + this.nameKeywords = nameKeywords; + } + + @Override + public boolean test(Person person) { + boolean nameCheck = nameKeywords.isEmpty() || nameKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + + boolean phoneCheck = phoneKeywords.isEmpty() || phoneKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getPhone().value, keyword)); + + boolean emailCheck = emailKeywords.isEmpty() || emailKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getEmail().value, keyword)); + + boolean addressCheck = addressKeywords.isEmpty() || addressKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getAddress().value, keyword)); + + boolean tagCheck = tagKeywords.isEmpty() || tagKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getTagsString(), keyword)); + + boolean statusCheck = statusKeywords.isEmpty() || statusKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getStatus().toString(), keyword)); + + boolean positionCheck = positionKeywords.isEmpty() || positionKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getPositionsString(), keyword)); + + return nameCheck && phoneCheck && emailCheck && addressCheck && tagCheck && statusCheck && positionCheck; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindCandidateCommandPredicate // instanceof handles nulls + && nameKeywords.equals(((FindCandidateCommandPredicate) other).nameKeywords) // state check + && phoneKeywords.equals(((FindCandidateCommandPredicate) other).phoneKeywords) + && addressKeywords.equals(((FindCandidateCommandPredicate) other).addressKeywords) + && tagKeywords.equals(((FindCandidateCommandPredicate) other).tagKeywords) + && statusKeywords.equals(((FindCandidateCommandPredicate) other).statusKeywords) + && positionKeywords.equals(((FindCandidateCommandPredicate) other).positionKeywords)); + } + + public void setNameKeywords(List<String> name) { + this.nameKeywords = name; + } + + public void setPhoneKeywords(List<String> phone) { + this.phoneKeywords = phone; + } + + public void setEmailKeywords(List<String> email) { + this.emailKeywords = email; + } + + public void setAddressKeywords(List<String> address) { + this.addressKeywords = address; + } + + public void setStatusKeywords(List<String> status) { + this.statusKeywords = status; + } + + public void setTagKeywords(List<String> tags) { + this.tagKeywords = tags; + } + + public void setPositionKeywords(List<String> positionKeywords) { + this.positionKeywords = positionKeywords; + } + + /** + * If any field is provided. + * @return if any field is provided. + */ + public boolean isAnyField() { + return !Stream.of(nameKeywords, phoneKeywords, emailKeywords, addressKeywords, statusKeywords, + tagKeywords, positionKeywords) + .allMatch(List::isEmpty); + } + +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 79244d71cf7..fe6dcb99ef2 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -4,7 +4,7 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Person's name in the HR Manager. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427c..00000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate<Person> { - private final List<String> keywords; - - public NameContainsKeywordsPredicate(List<String> keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 8ff1d83fe89..cc57b837acd 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -7,10 +7,12 @@ import java.util.Objects; import java.util.Set; +import seedu.address.model.interview.Interview; +import seedu.address.model.position.Position; import seedu.address.model.tag.Tag; /** - * Represents a Person in the address book. + * Represents a Person in the HR Manager. * Guarantees: details are present and not null, field values are validated, immutable. */ public class Person { @@ -23,17 +25,25 @@ public class Person { // Data fields private final Address address; private final Set<Tag> tags = new HashSet<>(); + private final Remark remark; + private Status status; + private Set<Position> positions = new HashSet<>(); + private Set<Interview> interviews = new HashSet<>(); /** - * Every field must be present and not null. + * Every field must be present and not null. If status is null, defaults to applied */ - public Person(Name name, Phone phone, Email email, Address address, Set<Tag> tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Phone phone, Email email, Address address, Remark remark, Set<Tag> tags, Status status, + Set<Position> positions) { + requireAllNonNull(name, phone, email, address, tags, status, positions); this.name = name; this.phone = phone; this.email = email; this.address = address; this.tags.addAll(tags); + this.remark = remark; + this.status = status; + this.positions.addAll(positions); } public Name getName() { @@ -52,6 +62,14 @@ public Address getAddress() { return address; } + public Remark getRemark() { + return remark; + } + + public Status getStatus() { + return status; + } + /** * Returns an immutable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. @@ -60,6 +78,91 @@ public Set<Tag> getTags() { return Collections.unmodifiableSet(tags); } + /** + * Return a String representation of Tags without '[', ']' and ',' + */ + public String getTagsString() { + return tags.toString().replace("[", "") + .replace("]", "").replace(",", ""); + } + + /** + * Returns an immutable position set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set<Position> getPositions() { + return Collections.unmodifiableSet(positions); + } + + /** + * Return a String representation of Positions without '[', ']' and ',' + */ + public String getPositionsString() { + return positions.toString().replace("[", "") + .replace("]", "").replace(",", ""); + } + + public Set<Interview> getInterviews() { + return Collections.unmodifiableSet(interviews); + } + + public boolean appliedForPosition(Position p) { + return positions.stream().anyMatch(p::isSamePosition); + } + + public boolean hasInterview(Interview i) { + return interviews.stream().anyMatch(i::isSameInterview); + } + + public void addPosition(Position p) { + positions.add(p); + } + + public void deletePosition(Position p) { + positions.remove(p); + } + + /** + * Adds an interview into a person's list of interviews, and since the person has the upcoming added interview, + * this method always sets the status as scheduled. + * + * @param i interview object to be added to person + */ + public void addInterview(Interview i) { + interviews.add(i); + if (status == Status.APPLIED) { + status = Status.SCHEDULED; + } + } + + /** + * Deletes an interview from a person's list of interviews. If the person does not have any upcoming interview + * this method sets the status as applied. + * + * @param i + */ + public void deleteInterview(Interview i) { + interviews.remove(i); + if (interviews.isEmpty()) { + status = Status.APPLIED; + } + } + + /** + * Checks if a person is vacant by checking against interviews from a person's list of interviews. + * If the person has interviews with overlapping time, return false. + * + * @param toSchedule interview to be scheduled for person. + */ + public boolean isVacantFor(Interview toSchedule) { + for (Interview interview : interviews) { + if (interview.hasOverLapWith(toSchedule)) { + return false; + } + } + return true; + } + /** * Returns true if both persons have the same name. * This defines a weaker notion of equality between two persons. @@ -69,8 +172,7 @@ public boolean isSamePerson(Person otherPerson) { return true; } - return otherPerson != null - && otherPerson.getName().equals(getName()); + return otherPerson != null && otherPerson.getEmail().equals(getEmail()); } /** @@ -92,13 +194,18 @@ public boolean equals(Object other) { && otherPerson.getPhone().equals(getPhone()) && otherPerson.getEmail().equals(getEmail()) && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); + && otherPerson.getTags().equals(getTags()) + && otherPerson.getRemark().equals(getRemark()) + && otherPerson.getStatus().equals(getStatus()) + && otherPerson.getPositions().equals(getPositions()); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + // hashing with all fields does not work properly when loading from data + //return Objects.hash(name, phone, email, address, tags, remark, status, positions); + return Objects.hash(name, phone, email, address, tags, remark); } @Override @@ -110,14 +217,40 @@ public String toString() { .append("; Email: ") .append(getEmail()) .append("; Address: ") - .append(getAddress()); + .append(getAddress()) + .append("; Status: ") + .append(getStatus()); Set<Tag> tags = getTags(); if (!tags.isEmpty()) { builder.append("; Tags: "); tags.forEach(builder::append); } + + if (!remark.toString().equals("")) { + builder.append("; Remark: ").append(remark); + } + + Set<Position> positions = getPositions(); + if (!positions.isEmpty()) { + builder.append("; Positions: "); + positions.forEach(builder::append); + } + + Set<Interview> interviews = getInterviews(); + if (!interviews.isEmpty()) { + builder.append("; Interviews: "); + interviews.forEach(interview -> builder.append(interview.getDisplayString())); + } + return builder.toString(); } + public void setInterviews(Set<Interview> personInterviews) { + interviews.addAll(personInterviews); + } + + public void setPositions(Set<Position> personPositions) { + positions = personPositions; + } } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index 872c76b382f..277516294bd 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -4,7 +4,7 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Person's phone number in the HR Manager. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { diff --git a/src/main/java/seedu/address/model/person/Remark.java b/src/main/java/seedu/address/model/person/Remark.java new file mode 100644 index 00000000000..fc9089df569 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Remark.java @@ -0,0 +1,42 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Person's remark in the HR Manager. + * Guarantees: immutable; is always valid + */ +public class Remark { + + public static final String MESSAGE_CONSTRAINTS = "Remarks can take any values, and it should not be blank"; + + public final String value; + + /** + * Constructs a {@code Remark}. + * + * @param remark A remark. + */ + public Remark(String remark) { + requireNonNull(remark); + value = remark; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Remark // instanceof handles nulls + && value.equals(((Remark) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Status.java b/src/main/java/seedu/address/model/person/Status.java new file mode 100644 index 00000000000..4371841b245 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Status.java @@ -0,0 +1,78 @@ +package seedu.address.model.person; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public enum Status { + NONE(false), + APPLIED(false), + SCHEDULED(false), + INTERVIEWED(false), + ACCEPTED(false), + REJECTED(true), + WITHDRAWN(true); + + public static final String MESSAGE_CONSTRAINTS = "Status can take the values:\n0.NONE\n1." + + "APPLIED\n2.SCHEDULED" + + "\n3.INTERVIEWED\n4.ACCEPTED\n5.REJECTED\n6.WITHDRAWN"; + + private static final List<String> valid = new ArrayList<String>(Arrays.asList("NONE", "APPLIED", "SCHEDULED", + "INTERVIEWED", + "ACCEPTED", + "REJECTED", + "WITHDRAWN", "")); + + private boolean isCompleted; + + private Status(boolean isCompleted) { + this.isCompleted = isCompleted; + } + + + /** + * Returns the corresponding Status Enum give a valid input. + * + * @param statusInput String input. + * @return Status. + */ + public static Status parseStatus(String statusInput) { + statusInput = statusInput == null ? "" : statusInput; + switch (statusInput.toUpperCase()) { + case "": + case "APPLIED": + return Status.APPLIED; + case "SCHEDULED": + return Status.SCHEDULED; + case "INTERVIEWED": + return Status.INTERVIEWED; + case "ACCEPTED": + return Status.ACCEPTED; + case "REJECTED": + return Status.REJECTED; + case "WITHDRAWN": + return Status.WITHDRAWN; + default: + return Status.NONE; + } + } + + public static Boolean isValidStatus(String statusInput) { + return valid.contains(statusInput); + } + + + /** + * Returns if the status represent a completed state. + * + * @return isCompleted. + */ + public boolean isCompleted() { + return this.isCompleted; + } + + @Override + public String toString() { + return this.name(); + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 0fee4fe57e6..83fba3b9e81 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -8,6 +8,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; @@ -48,6 +49,10 @@ public void add(Person toAdd) { internalList.add(toAdd); } + public Person getPerson(Index index) { + return internalList.get(index.getZeroBased()); + } + /** * Replaces the person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the list. diff --git a/src/main/java/seedu/address/model/position/FindPositionCommandPredicate.java b/src/main/java/seedu/address/model/position/FindPositionCommandPredicate.java new file mode 100644 index 00000000000..e28529a0d42 --- /dev/null +++ b/src/main/java/seedu/address/model/position/FindPositionCommandPredicate.java @@ -0,0 +1,78 @@ +package seedu.address.model.position; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import seedu.address.commons.util.StringUtil; + + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + */ +public class FindPositionCommandPredicate implements Predicate<Position> { + + private List<String> titleKeywords = new ArrayList<String>(); + private List<String> positionStatusKeywords = new ArrayList<String>(); + + public FindPositionCommandPredicate() { + } + + + /** + * Alternative Constructor with only title field. + * @param titleKeyword + */ + public FindPositionCommandPredicate(List<String> titleKeyword) { + this.titleKeywords = titleKeyword; + } + + /** + * Alternative Constructor with keyword field. + * @param titleKeywords + * @param positionStatusKeywords + */ + public FindPositionCommandPredicate(List<String> titleKeywords, List<String> positionStatusKeywords) { + this.titleKeywords = titleKeywords; + this.positionStatusKeywords = positionStatusKeywords; + } + + @Override + public boolean test(Position position) { + boolean titleCheck = titleKeywords.isEmpty() || titleKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(position.getTitle().fullTitle, keyword)); + + boolean positionCheck = positionStatusKeywords.isEmpty() || positionStatusKeywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(position.getStatus().name(), keyword)); + + return titleCheck && positionCheck; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindPositionCommandPredicate // instanceof handles nulls + && titleKeywords.equals(((FindPositionCommandPredicate) other).titleKeywords) // state check + && positionStatusKeywords.equals(((FindPositionCommandPredicate) other).positionStatusKeywords)); + } + + public void setTitleKeywords(List<String> titleKeywords) { + this.titleKeywords = titleKeywords; + } + + public void setPositionStatusKeywords(List<String> positionStatusKeywords) { + this.positionStatusKeywords = positionStatusKeywords; + } + + + /** + * If any field is provided. + * @return if any field is provided. + */ + public boolean isAnyField() { + return !Stream.of(titleKeywords, positionStatusKeywords) + .allMatch(List::isEmpty); + } + +} diff --git a/src/main/java/seedu/address/model/position/Position.java b/src/main/java/seedu/address/model/position/Position.java new file mode 100644 index 00000000000..b58ee65da2e --- /dev/null +++ b/src/main/java/seedu/address/model/position/Position.java @@ -0,0 +1,117 @@ +package seedu.address.model.position; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Position in the HR Manager, with default status 'open'. + * Guarantees: immutable; title is valid and not null. + */ +public class Position { + + public static final String MESSAGE_CONSTRAINTS = "Position names should be alphanumeric"; + public static final String MESSAGE_POSITION_CLOSED = "Position %s is closed"; + public static final String MESSAGE_POSITION_DOES_NOT_EXIST = "Position %s not found in HR Manager"; + + public final Title title; + + private PositionStatus status; + + public enum PositionStatus { + OPEN, + CLOSED; + + public static final String MESSAGE_CONSTRAINTS = "Position Status can only take the values:\n" + + "open\n" + + "closed\n"; + + /* + * The first character of the status must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + /** + * Returns true if a given string is a valid status. + * Only "open" and "closed" are valid strings for status. + */ + public static boolean isValidPositionStatus(String test) { + return (((test.toLowerCase()).equals("open") || (test.toLowerCase()).equals("closed")) + && test.matches(VALIDATION_REGEX)); + } + } + + /** + * Constructs a {@code Position}. + * + * @param title A valid position title. + */ + public Position(Title title) { + requireNonNull(title); + this.title = title; + this.status = PositionStatus.OPEN; + } + + /** + * Constructs a {@code Position} + * + * @param title A valid position title. + * @param status A valid position status. + */ + public Position(Title title, PositionStatus status) { + requireNonNull(title); + this.title = title; + this.status = status; + } + + /** + * Returns true if both positions have the same name. + * This defines a weaker notion of equality between two positions. + */ + public boolean isSamePosition(Position otherPosition) { + if (otherPosition == this) { + return true; + } + + return otherPosition != null + && otherPosition.getTitle().equals(getTitle()); + } + + public Title getTitle() { + return this.title; + } + + public PositionStatus getStatus() { + if (this.status == null) { + setStatus(PositionStatus.OPEN); + } + return this.status; + } + + public void setStatus(PositionStatus status) { + this.status = status; + } + + public boolean isClosed() { + return this.status.equals(PositionStatus.CLOSED); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Position // instanceof handles nulls + && title.equals(((Position) other).title) + && status == ((Position) other).getStatus()); // status check + } + + @Override + public int hashCode() { + return title.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + title.toString() + ']'; + } +} diff --git a/src/main/java/seedu/address/model/position/Title.java b/src/main/java/seedu/address/model/position/Title.java new file mode 100644 index 00000000000..3d484da9c1b --- /dev/null +++ b/src/main/java/seedu/address/model/position/Title.java @@ -0,0 +1,60 @@ +package seedu.address.model.position; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Position's title in the HR Manager. + * Guarantees: immutable; is valid as declared in {@link #isValidTitle(String)} + */ +public class Title { + + public static final String MESSAGE_CONSTRAINTS = + "Titles should only contain alphanumeric characters and 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 final String fullTitle; + + /** + * Constructs a {@code Title}. + * + * @param title A valid title. + */ + public Title(String title) { + requireNonNull(title); + checkArgument(isValidTitle(title), MESSAGE_CONSTRAINTS); + fullTitle = title; + } + + /** + * Returns true if a given string is a valid title. + */ + public static boolean isValidTitle(String test) { + return test.matches(VALIDATION_REGEX); + } + + + @Override + public String toString() { + return fullTitle; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Title // instanceof handles nulls + && fullTitle.equalsIgnoreCase(((Title) other).fullTitle)); // state check + } + + @Override + public int hashCode() { + return fullTitle.hashCode(); + } + +} + diff --git a/src/main/java/seedu/address/model/position/TitleContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/position/TitleContainsKeywordsPredicate.java new file mode 100644 index 00000000000..63b59f6fae7 --- /dev/null +++ b/src/main/java/seedu/address/model/position/TitleContainsKeywordsPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model.position; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Position}'s {@code Title} matches any of the keywords given. + */ +public class TitleContainsKeywordsPredicate implements Predicate<Position> { + private final List<String> keywords; + + public TitleContainsKeywordsPredicate(List<String> keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Position position) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(position.getTitle().fullTitle, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + // instanceof handles nulls + || (other instanceof TitleContainsKeywordsPredicate + // state check + && keywords.equals(((TitleContainsKeywordsPredicate) other).keywords)); + } + +} diff --git a/src/main/java/seedu/address/model/position/UniquePositionList.java b/src/main/java/seedu/address/model/position/UniquePositionList.java new file mode 100644 index 00000000000..21f585737ba --- /dev/null +++ b/src/main/java/seedu/address/model/position/UniquePositionList.java @@ -0,0 +1,163 @@ +package seedu.address.model.position; + +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.position.exceptions.DuplicatePositionException; +import seedu.address.model.position.exceptions.PositionNotFoundException; + +/** + * A list of positions that enforces uniqueness between its elements and does not allow nulls. + * A position is considered unique by comparing using {@code Position#isSamePosition(Position)}. As such, adding and + * updating of positions uses Position#isSamePosition(Position) for equality so as to ensure that the position being + * added or updated is unique in terms of identity in the UniquePositionList. However, the removal of a position uses + * Position#equals(Object) so as to ensure that the position with exactly the same fields will be removed. + * <p> + * Supports a minimal set of list operations. + * + * @see Position#isSamePosition(Position) + */ +public class UniquePositionList implements Iterable<Position> { + + private final ObservableList<Position> internalList = FXCollections.observableArrayList(); + private final ObservableList<Position> internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent position as the given argument. + */ + public boolean contains(Position toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSamePosition); + } + + /** + * Adds a position to the list. + * The position must not already exist in the list. + */ + public void add(Position toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePositionException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the position {@code target} in the list with {@code editedPosition}. + * {@code target} must exist in the list. + * The position identity of {@code editedPosition} must not be the same as another existing position in the list. + */ + public void setPosition(Position target, Position editedPosition) { + requireAllNonNull(target, editedPosition); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PositionNotFoundException(); + } + + if (!target.isSamePosition(editedPosition) && contains(editedPosition)) { + throw new DuplicatePositionException(); + } + + internalList.set(index, editedPosition); + } + + /** + * Removes the equivalent position from the list. + * The position must exist in the list. + */ + public void remove(Position toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new PositionNotFoundException(); + } + } + + public void setPositions(UniquePositionList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code positions}. + * {@code positions} must not contain duplicate positions. + */ + public void setPositions(List<Position> positions) { + requireAllNonNull(positions); + if (!positionsAreUnique(positions)) { + throw new DuplicatePositionException(); + } + + internalList.setAll(positions); + } + + + public Position getPosition(Position position) { + assert internalList.contains(position); + + for (Position p : internalList) { + if (p.isSamePosition(position)) { + return p; + } + } + return null; + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<Position> asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator<Position> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniquePositionList // instanceof handles nulls + && internalList.equals(((UniquePositionList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code positions} contains only unique positions. + */ + private boolean positionsAreUnique(List<Position> positions) { + for (int i = 0; i < positions.size() - 1; i++) { + for (int j = i + 1; j < positions.size(); j++) { + if (positions.get(i).isSamePosition(positions.get(j))) { + return false; + } + } + } + return true; + } + + /** + * Check if a position in the list is closed. + * @param toCheck The position to check. + */ + public boolean positionIsClosed(Position toCheck) { + for (Position p : internalList) { + if (p.isSamePosition(toCheck)) { + return p.isClosed(); + } + } + return true; + } +} + diff --git a/src/main/java/seedu/address/model/position/exceptions/DuplicatePositionException.java b/src/main/java/seedu/address/model/position/exceptions/DuplicatePositionException.java new file mode 100644 index 00000000000..6613fa799d7 --- /dev/null +++ b/src/main/java/seedu/address/model/position/exceptions/DuplicatePositionException.java @@ -0,0 +1,11 @@ +package seedu.address.model.position.exceptions; + +/** + * Signals that the operation will result in duplicate Positions (Positions are considered duplicates if they have the + * same identity). + */ +public class DuplicatePositionException extends RuntimeException { + public DuplicatePositionException() { + super("Operation would result in duplicate positions"); + } +} diff --git a/src/main/java/seedu/address/model/position/exceptions/PositionNotFoundException.java b/src/main/java/seedu/address/model/position/exceptions/PositionNotFoundException.java new file mode 100644 index 00000000000..d579d2e10fe --- /dev/null +++ b/src/main/java/seedu/address/model/position/exceptions/PositionNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.position.exceptions; + +/** + * Signals that the operation is unable to find the specified position. + */ +public class PositionNotFoundException extends RuntimeException { } diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index b0ea7e7dad7..a9af690ed6f 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -4,7 +4,7 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Tag in the address book. + * Represents a Tag in the Hr Manager. * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ public class Tag { diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..bfb09dcf171 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,51 +1,126 @@ package seedu.address.model.util; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; import java.util.Arrays; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.HrManager; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.model.interview.Interview; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; +import seedu.address.model.person.Status; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; import seedu.address.model.tag.Tag; /** - * Contains utility methods for populating {@code AddressBook} with sample data. + * Contains utility methods for populating {@code HrManager} with sample data. */ public class SampleDataUtil { + + public static final Remark EMPTY_REMARK = new Remark(""); + + private static final Person ALEX = new Person(new Name("Alex Yeoh"), new Phone("87438807"), + new Email("alexyeoh@example.com"), + new Address("Blk 30 Geylang Street 29, #06-40"), EMPTY_REMARK, + new HashSet<>(), Status.APPLIED, getPositionSet("Bookkeeper")); + + private static final Person BERNICE = new Person(new Name("Bernice Yu"), new Phone("99272758"), + new Email("berniceyu@example.com"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), EMPTY_REMARK, + new HashSet<>(), Status.ACCEPTED, getPositionSet("Admin Assistant", "Bookkeeper")); + + private static final Person CHARLOTTE = 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"), EMPTY_REMARK, + new HashSet<>(), Status.INTERVIEWED, getPositionSet("Accountant")); + + private static final Person DAVID = new Person(new Name("David Li"), new Phone("91031282"), + new Email("lidavid@example.com"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), EMPTY_REMARK, + new HashSet<>(), Status.NONE, getPositionSet("Senior Engineer")); + + private static final Person IRFAN = new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), + new Email("irfan@example.com"), + new Address("Blk 47 Tampines Street 20, #17-35"), EMPTY_REMARK, + new HashSet<>(), Status.REJECTED, getPositionSet("Project Manager", "Sales Representative")); + + private static final Person ROY = new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), + new Email("royb@example.com"), + new Address("Blk 45 Aljunied Street 85, #11-31"), EMPTY_REMARK, + new HashSet<>(), Status.REJECTED, getPositionSet("Sales Representative")); + + private static final Position BOOKKEEPER = new Position(new Title("Bookkeeper")); + private static final Position ADMIN_ASSISTANT = new Position(new Title("Admin Assistant")); + private static final Position PROJECT_MANAGER = new Position(new Title("Project Manager")); + private static final Position SALES_REP = new Position(new Title("Sales Representative")); + private static final Position SEN_ENGINEER = new Position(new Title("Senior Engineer")); + private static final Position ACCOUNTANT = new Position(new Title("Accountant")); + private static final Position SECRETARY = new Position(new Title("Secretary"), + Position.PositionStatus.CLOSED); + private static final Position SECURITY_GUARD = new Position(new Title("Security Guard"), + Position.PositionStatus.CLOSED); + 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")) + ALEX, BERNICE, CHARLOTTE, DAVID, IRFAN, ROY + }; + } + + public static Position[] getSamplePositions() { + return new Position[] { + BOOKKEEPER, ADMIN_ASSISTANT, PROJECT_MANAGER, SALES_REP, SEN_ENGINEER, ACCOUNTANT, SECRETARY, SECURITY_GUARD }; } - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); + public static Interview[] getSampleInterviews() { + Set<Person> sampleCandidatesA = new HashSet<>(); + sampleCandidatesA.add(IRFAN); + sampleCandidatesA.add(ROY); + + Set<Person> sampleCandidatesB = new HashSet<>(); + sampleCandidatesB.add(ALEX); + sampleCandidatesB.add(BERNICE); + + + return new Interview[] { + new Interview(SALES_REP, sampleCandidatesA, LocalDate.of(2021, 10, 15), + LocalTime.of(10, 0), Duration.ofHours(1), Interview.InterviewStatus.PENDING), + new Interview(BOOKKEEPER, sampleCandidatesB, LocalDate.of(2021, 10, 26), + LocalTime.of(12, 30), Duration.ofMinutes(45), Interview.InterviewStatus.PENDING) + }; + } + + + public static ReadOnlyHrManager getSampleHrManager() { + HrManager sampleHm = new HrManager(); for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + sampleHm.addPerson(samplePerson); + } + for (Position samplePosition : getSamplePositions()) { + sampleHm.addPosition(samplePosition); + } + for (Interview sampleInterview : getSampleInterviews()) { + Set<Person> candidates = sampleInterview.getCandidates(); + for (Person person : sampleHm.getPersonList()) { + for (Person candidate : candidates) { + if (candidate.equals(person)) { + person.addInterview(sampleInterview); + } + } + } + sampleHm.addInterview(sampleInterview); } - return sampleAb; + return sampleHm; } /** @@ -57,4 +132,10 @@ public static Set<Tag> getTagSet(String... strings) { .collect(Collectors.toSet()); } + public static Set<Position> getPositionSet(String... strings) { + return Arrays.stream(strings) + .map(Title::new) + .map(Position::new) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f9..00000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional<ReadOnlyAddressBook> readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/HrManagerStorage.java b/src/main/java/seedu/address/storage/HrManagerStorage.java new file mode 100644 index 00000000000..2a829fe43a2 --- /dev/null +++ b/src/main/java/seedu/address/storage/HrManagerStorage.java @@ -0,0 +1,59 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.HrManager; +import seedu.address.model.ReadOnlyHrManager; + +/** + * Represents a storage for {@link HrManager}. + */ +public interface HrManagerStorage { + + /** + * Returns the file path of the data file for candidates. + */ + Path getHrManagerCandidatesFilePath(); + + /** + * Returns HR Manager data as a {@link ReadOnlyHrManager}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional<ReadOnlyHrManager> readHrManager() throws DataConversionException, IOException; + + /** + * @see #getHrManagerCandidatesFilePath() + */ + Optional<ReadOnlyHrManager> readHrManager(Path candidatesFilePath, + Path positionsFilePath, + Path interviewsFilePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyHrManager} to the storage. + * @param hrManager cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveHrManager(ReadOnlyHrManager hrManager) throws IOException; + + /** + * @see #saveHrManager(ReadOnlyHrManager) + */ + void saveHrManager(ReadOnlyHrManager hrManager, Path candidatesFilePath, Path positionsFilePath, + Path interviewsFilePath) throws IOException; + + /** + * Returns the file path of the data file for Positions. + */ + Path getHrManagerPositionsFilePath(); + + /** + * Returns the file path of the data file for Interviews. + */ + Path getHrManagerInterviewsFilePath(); + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedInterview.java b/src/main/java/seedu/address/storage/JsonAdaptedInterview.java new file mode 100644 index 00000000000..3de21b85f52 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedInterview.java @@ -0,0 +1,196 @@ +package seedu.address.storage; + +import java.time.DateTimeException; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +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.logic.parser.exceptions.ParseException; +import seedu.address.model.interview.Interview; +import seedu.address.model.interview.Interview.InterviewStatus; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; + +public class JsonAdaptedInterview { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Interview's %s field is missing!"; + + private final String position; + + private final Set<String> candidateIDs = new HashSet<>(); + + private final String startTime; + + private final String date; + + private final String duration; + + private final InterviewStatus status; + + /** + * Constructs a {@code JsonAdaptedInterview} with the given {@code positionName}. + */ + @JsonCreator + public JsonAdaptedInterview(@JsonProperty("position") String position, + @JsonProperty("candidateIDs") Set<String> candidateIDs, + @JsonProperty("date") String date, + @JsonProperty("startTime") String startTime, + @JsonProperty("duration") String duration, + @JsonProperty("status") InterviewStatus status) { + this.position = position; + if (candidateIDs != null) { + this.candidateIDs.addAll(candidateIDs); + } + this.date = date; + this.startTime = startTime; + this.duration = duration; + this.status = status; + } + + + /** + * Converts a given {@code Position} into this class for Jackson use. + */ + public JsonAdaptedInterview(Interview source) { + position = source.getPositionTitle().fullTitle; + candidateIDs.addAll(source.getCandidates().stream() + .map(s -> String.valueOf(s.hashCode())) + .collect(Collectors.toList())); + date = source.getDateInFormattedString(); + startTime = source.getTimeInFormattedString(); + duration = source.getDurationInFormattedString(); + status = source.getStatus(); + } + + /** + * Parses {@code String date} into a {@code LocalDate}. + * @param date Input String. + * @return LocalDate of an Interview. + * @throws ParseException If the given {@code date} is invalid + */ + public static LocalDate parseDate(String date) throws ParseException { + String dateFormat = "^[0-9]{1,2}[\\\\/][0-9]{1,2}[\\\\/][0-9]{4}$"; + Pattern p = Pattern.compile(dateFormat); + Matcher m = p.matcher(date); + if (m.find()) { + String[] foundDate = m.group().split("/"); + int year = Integer.parseInt(foundDate[2]); + int month = Integer.parseInt(foundDate[1]); + int day = Integer.parseInt(foundDate[0]); + try { + return LocalDate.of(year, month, day); + } catch (DateTimeException e) { + throw new ParseException(Interview.MESSAGE_DATE_CONSTRAINTS); + } + } + throw new ParseException(Interview.MESSAGE_DATE_CONSTRAINTS); + } + + /** + * Parses {@code String time} into a {@code LocalTime}. + * @param time Input String. + * @return LocalTime of an Interview. + * @throws ParseException If the given {@code time} is invalid + */ + public static LocalTime parseTime(String time) throws ParseException { + String timeFormat = "^[0-9]{4}$"; + Pattern p = Pattern.compile(timeFormat); + Matcher m = p.matcher(time); + if (m.find()) { + int hour = Integer.parseInt(time.substring(0, 2)); + int min = Integer.parseInt(time.substring(2)); + try { + return LocalTime.of(hour, min); + } catch (DateTimeException e) { + throw new ParseException(Interview.MESSAGE_TIME_CONSTRAINTS); + } + } + throw new ParseException(Interview.MESSAGE_TIME_CONSTRAINTS); + } + + /** + * Parses {@code String duration} into a {@code Duration}. + * @param duration Input String. + * @return Duration of an Interview. + * @throws ParseException If the given {@code duration} is invalid + */ + public static Duration parseDuration(String duration) throws ParseException { + try { + Long actualDuration = Long.parseLong(duration); + //capped at strictly less than 24 hours or 1440 minutes + if (actualDuration <= 0 || actualDuration >= 1440) { + throw new ParseException(Interview.MESSAGE_DURATION_CONSTRAINTS_INVALID_NUMBER); + } + return Duration.ofMinutes(actualDuration); + } catch (NumberFormatException e) { + throw new ParseException(Interview.MESSAGE_DURATION_CONSTRAINTS_NOT_A_NUMBER); + } + } + + + + /** + * Converts this Jackson-friendly adapted position object into the model's {@code Position} object. + * @return An Interview loaded from Json file and yet to have actual candidates added. + * @throws IllegalValueException if there were any data constraints violated in the adapted position. + */ + public Interview toModelType() throws IllegalValueException { + if (position == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Position.class.getSimpleName())); + } + if (!Title.isValidTitle(position)) { + throw new IllegalValueException(Title.MESSAGE_CONSTRAINTS); + } + + final Set<Integer> personHashCodes = new HashSet<>(); + for (String id : candidateIDs) { + personHashCodes.add(Integer.parseInt(id)); + } + + if (date == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "date")); + } + LocalDate localDate; + try { + localDate = parseDate(date); + } catch (ParseException e) { + throw new IllegalValueException(Interview.MESSAGE_DATE_CONSTRAINTS); + } + if (startTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "time")); + } + LocalTime localTime; + try { + localTime = parseTime(startTime); + } catch (ParseException e) { + throw new IllegalValueException(Interview.MESSAGE_TIME_CONSTRAINTS); + } + if (duration == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "duration")); + } + Duration duration; + try { + duration = parseDuration(this.duration); + } catch (ParseException e) { + //getMessage() is used because parseDuration can throw an exception with 2 different messages + throw new IllegalValueException(e.getMessage()); + } + if (status == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + InterviewStatus.class.getSimpleName())); + } + Interview interview = new Interview(new Position(new Title(position), + Position.PositionStatus.OPEN), new HashSet<>(), localDate, localTime, duration, status); + interview.setCandidateIDs(personHashCodes); + return interview; + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index a6321cec2ea..9462f0b0658 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -15,8 +15,12 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; +import seedu.address.model.person.Status; +import seedu.address.model.position.Position; import seedu.address.model.tag.Tag; + /** * Jackson-friendly version of {@link Person}. */ @@ -28,22 +32,32 @@ class JsonAdaptedPerson { private final String phone; private final String email; private final String address; + private final String remark; private final List<JsonAdaptedTag> tagged = new ArrayList<>(); + private final String status; + private final List<JsonAdaptedPosition> positions = new ArrayList<>(); /** * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List<JsonAdaptedTag> tagged) { + @JsonProperty("email") String email, @JsonProperty("address") String address, + @JsonProperty("remark") String remark, @JsonProperty("tagged") List<JsonAdaptedTag> tagged, + @JsonProperty("status") String status, + @JsonProperty("positions") List<JsonAdaptedPosition> positions) { this.name = name; this.phone = phone; this.email = email; this.address = address; + this.remark = remark; if (tagged != null) { this.tagged.addAll(tagged); } + this.status = status; + if (positions != null) { + this.positions.addAll(positions); + } } /** @@ -54,9 +68,14 @@ public JsonAdaptedPerson(Person source) { phone = source.getPhone().value; email = source.getEmail().value; address = source.getAddress().value; + remark = source.getRemark().value; tagged.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); + status = source.getStatus().toString(); + positions.addAll(source.getPositions().stream() + .map(JsonAdaptedPosition::new) + .collect(Collectors.toList())); } /** @@ -70,6 +89,11 @@ public Person toModelType() throws IllegalValueException { personTags.add(tag.toModelType()); } + final List<Position> personPositions = new ArrayList<>(); + for (JsonAdaptedPosition position : positions) { + personPositions.add(position.toModelType()); + } + if (name == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); } @@ -101,9 +125,22 @@ public Person toModelType() throws IllegalValueException { throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); } final Address modelAddress = new Address(address); - + if (remark == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Remark.class.getSimpleName())); + } + final Remark modelRemark = new Remark(remark); final Set<Tag> modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + final Set<Position> modelPositions = new HashSet<>(personPositions); + + if (status == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Status.class.getSimpleName())); + } + final Status modelStatus = Status.parseStatus(status); + + Person p = new Person(modelName, modelPhone, modelEmail, modelAddress, modelRemark, + modelTags, modelStatus, modelPositions); + + return p; } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPosition.java b/src/main/java/seedu/address/storage/JsonAdaptedPosition.java new file mode 100644 index 00000000000..c51c21d1b62 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedPosition.java @@ -0,0 +1,60 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.position.Position; +import seedu.address.model.position.Position.PositionStatus; +import seedu.address.model.position.Title; + +public class JsonAdaptedPosition { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Position's %s field is missing!"; + + private final String title; + + private final PositionStatus positionStatus; + + /** + * Constructs a {@code JsonAdaptedPosition} with the given {@code positionName}. + */ + @JsonCreator + public JsonAdaptedPosition(@JsonProperty("title") String title, + @JsonProperty("positionStatus") PositionStatus positionStatus) { + this.title = title; + this.positionStatus = positionStatus; + } + + /** + * Converts a given {@code Position} into this class for Jackson use. + */ + public JsonAdaptedPosition(Position source) { + title = source.getTitle().fullTitle; + positionStatus = source.getStatus(); + } + + public String getTitle() { + return title; + } + + /** + * Converts this Jackson-friendly adapted position object into the model's {@code Position} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted position. + */ + public Position toModelType() throws IllegalValueException { + if (title == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName())); + } + if (!Title.isValidTitle(title)) { + throw new IllegalValueException(Title.MESSAGE_CONSTRAINTS); + } + + final Title modelTitle = new Title(title); + + Position position = new Position(modelTitle); + position.setStatus(positionStatus); + return position; + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTitle.java b/src/main/java/seedu/address/storage/JsonAdaptedTitle.java new file mode 100644 index 00000000000..ff316b25095 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTitle.java @@ -0,0 +1,43 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.position.Title; + +public class JsonAdaptedTitle { + private final String title; + + /** + * Constructs a {@code JsonAdaptedTitle} with the given {@code title}. + */ + @JsonCreator + public JsonAdaptedTitle(String title) { + this.title = title; + } + + /** + * Converts a given {@code JsonAdaptedTitle} into this class for Jackson use. + */ + public JsonAdaptedTitle(Title source) { + title = source.fullTitle; + } + + @JsonValue + public String getTitle() { + return title; + } + + /** + * Converts this Jackson-friendly adapted title object into the model's {@code Title} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted title. + */ + public Title toModelType() throws IllegalValueException { + if (!Title.isValidTitle(title)) { + throw new IllegalValueException(Title.MESSAGE_CONSTRAINTS); + } + return new Title(title); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java deleted file mode 100644 index dfab9daaa0d..00000000000 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as a json file on the hard disk. - */ -public class JsonAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); - - private Path filePath; - - public JsonAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()}. - * - * @param filePath location of the data. Cannot be null. - * @throws DataConversionException if the file is not in the correct format. - */ - public Optional<ReadOnlyAddressBook> readAddressBook(Path filePath) throws DataConversionException { - requireNonNull(filePath); - - Optional<JsonSerializableAddressBook> jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { - return Optional.empty(); - } - - try { - return Optional.of(jsonAddressBook.get().toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. - * - * @param filePath location of the data. Cannot be null. - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonHrManagerStorage.java b/src/main/java/seedu/address/storage/JsonHrManagerStorage.java new file mode 100644 index 00000000000..dcd5bb3ca6f --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonHrManagerStorage.java @@ -0,0 +1,159 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.FileUtil; +import seedu.address.commons.util.JsonUtil; +import seedu.address.model.HrManager; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; + +/** + * A class to access HrManager data stored as a json file on the hard disk. + */ +public class JsonHrManagerStorage implements HrManagerStorage { + + + private static final Logger logger = LogsCenter.getLogger(JsonHrManagerStorage.class); + + private Path candidatesFilePath; + private Path positionsFilePath; + private Path interviewsFilePath; + + /** + * Constructs a {@code JsonHrManagerStorage} with the given paths. + */ + public JsonHrManagerStorage(Path candidatesFilePath, Path positionsFilePath, Path interviewsFilePath) { + this.candidatesFilePath = candidatesFilePath; + this.positionsFilePath = positionsFilePath; + this.interviewsFilePath = interviewsFilePath; + } + + public Path getHrManagerCandidatesFilePath() { + return candidatesFilePath; + } + + public Path getHrManagerPositionsFilePath() { + return positionsFilePath; + } + + public Path getHrManagerInterviewsFilePath() { + return interviewsFilePath; + } + + @Override + public Optional<ReadOnlyHrManager> readHrManager() throws DataConversionException { + return readHrManager(candidatesFilePath, positionsFilePath, interviewsFilePath); + } + + /** + * Similar to {@link #readHrManager()}. + * + * @param candidatesFilePath location of the data. Cannot be null. + * @param positionsFilePath location of the data. Cannot be null. + * @param interviewsFilePath location of the data. Cannot be null. + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional<ReadOnlyHrManager> readHrManager(Path candidatesFilePath, + Path positionsFilePath, + Path interviewsFilePath) throws DataConversionException { + requireNonNull(candidatesFilePath); + requireNonNull(positionsFilePath); + + Optional<JsonSerializableHrManagerCandidates> candidates = JsonUtil.readJsonFile( + candidatesFilePath, JsonSerializableHrManagerCandidates.class); + + Optional<JsonSerializableHrManagerPositions> positions = JsonUtil.readJsonFile( + positionsFilePath, JsonSerializableHrManagerPositions.class); + + Optional<JsonSerializableHrManagerInterviews> interviews = JsonUtil.readJsonFile( + interviewsFilePath, JsonSerializableHrManagerInterviews.class); + + //merge data from all files + try { + HrManager merge = new HrManager(); + if (candidates.isPresent()) { + for (Person person : candidates.get().toModelType().getPersonList()) { + merge.addPerson(person); + } + } + if (positions.isPresent()) { + for (Position position : positions.get().toModelType().getPositionList()) { + merge.addPosition(position); + } + } + + //add candidate to interviews as well as add interview data + if (interviews.isPresent()) { + for (Interview interview : interviews.get().toModelType().getInterviewList()) { + Set<Integer> uniqueIds = interview.getCandidateIDs(); + Set<Person> candidateSet = new HashSet<>(); + for (Person person : merge.getPersonList()) { + for (Integer integer : uniqueIds) { + if (integer.equals(person.hashCode())) { + candidateSet.add(person); + person.addInterview(interview); + } + } + } + interview.setCandidates(candidateSet); + merge.addInterview(interview); + } + } + + if (candidates.isEmpty() && positions.isEmpty() && interviews.isEmpty()) { + return Optional.empty(); + } + return Optional.of(merge); + } catch (IllegalValueException ive) { + logger.info("Illegal values found when merging data from " + candidatesFilePath + ", " + + positionsFilePath + " and " + interviewsFilePath + ":" + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void saveHrManager(ReadOnlyHrManager hrManager) throws IOException { + saveHrManager(hrManager, candidatesFilePath, positionsFilePath, interviewsFilePath); + } + + /** + * Similar to {@link #saveHrManager(ReadOnlyHrManager)}. + * + * @param hrManager cannot be null. + * @param candidatesFilePath location of the data. Cannot be null. + * @param positionsFilePath location of the data. Cannot be null. + * @param interviewsFilePath location of the data. Cannot be null. + */ + public void saveHrManager(ReadOnlyHrManager hrManager, Path candidatesFilePath, + Path positionsFilePath, Path interviewsFilePath) throws IOException { + requireNonNull(hrManager); + requireNonNull(candidatesFilePath); + requireNonNull(positionsFilePath); + requireNonNull(interviewsFilePath); + + //save candidates + FileUtil.createIfMissing(candidatesFilePath); + JsonUtil.saveJsonFile(new JsonSerializableHrManagerCandidates(hrManager), candidatesFilePath); + + //save positions + FileUtil.createIfMissing(positionsFilePath); + JsonUtil.saveJsonFile(new JsonSerializableHrManagerPositions(hrManager), positionsFilePath); + + //save interviews + FileUtil.createIfMissing(interviewsFilePath); + JsonUtil.saveJsonFile(new JsonSerializableHrManagerInterviews(hrManager), interviewsFilePath); + } +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableHrManagerCandidates.java similarity index 52% rename from src/main/java/seedu/address/storage/JsonSerializableAddressBook.java rename to src/main/java/seedu/address/storage/JsonSerializableHrManagerCandidates.java index 5efd834091d..53e4753d871 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableHrManagerCandidates.java @@ -9,52 +9,51 @@ import com.fasterxml.jackson.annotation.JsonRootName; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.HrManager; +import seedu.address.model.ReadOnlyHrManager; import seedu.address.model.person.Person; /** - * An Immutable AddressBook that is serializable to JSON format. + * An Immutable HrManager that is serializable to JSON format. */ -@JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { - +@JsonRootName(value = "HrManagerCandidates") +public class JsonSerializableHrManagerCandidates { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; private final List<JsonAdaptedPerson> persons = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableHrManagerCandidates} with the given persons. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List<JsonAdaptedPerson> persons) { + public JsonSerializableHrManagerCandidates(@JsonProperty("persons") List<JsonAdaptedPerson> persons) { this.persons.addAll(persons); } /** - * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. + * Converts a given {@code ReadOnlyHrManager} into this class for Jackson use. * - * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. + * @param source future changes to this will not affect the created {@code JsonSerializableHrManagerCandidates}. */ - public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); + public JsonSerializableHrManagerCandidates(ReadOnlyHrManager source) { + persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new) + .collect(Collectors.toList())); } /** - * Converts this address book into the model's {@code AddressBook} object. + * Converts this serializable HrManagerCandidates into the model's {@code HrManager} object. * * @throws IllegalValueException if there were any data constraints violated. */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); + public HrManager toModelType() throws IllegalValueException { + HrManager hrManager = new HrManager(); for (JsonAdaptedPerson jsonAdaptedPerson : persons) { Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { + if (hrManager.hasPerson(person)) { throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); } - addressBook.addPerson(person); + hrManager.addPerson(person); } - return addressBook; + return hrManager; } - } diff --git a/src/main/java/seedu/address/storage/JsonSerializableHrManagerInterviews.java b/src/main/java/seedu/address/storage/JsonSerializableHrManagerInterviews.java new file mode 100644 index 00000000000..8a563c43435 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonSerializableHrManagerInterviews.java @@ -0,0 +1,59 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.HrManager; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.model.interview.Interview; + +/** + * An Immutable HrManager that is serializable to JSON format. + */ +@JsonRootName(value = "HrManagerInterviews") +public class JsonSerializableHrManagerInterviews { + public static final String MESSAGE_DUPLICATE_INTERVIEWS = "Interviews list contains duplicate interview(s)."; + + private final List<JsonAdaptedInterview> interviews = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableHrManagerInterviews} with the given interviews. + */ + @JsonCreator + public JsonSerializableHrManagerInterviews(@JsonProperty("interviews") List<JsonAdaptedInterview> interviews) { + this.interviews.addAll(interviews); + } + + /** + * Converts a given {@code HrManager} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableHrManagerInterviews}. + */ + public JsonSerializableHrManagerInterviews(ReadOnlyHrManager source) { + interviews.addAll(source.getInterviewList().stream().map(JsonAdaptedInterview::new) + .collect(Collectors.toList())); + } + + /** + * Converts this serializable HrManagerInterviews into the model's {@code HrManager} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public HrManager toModelType() throws IllegalValueException { + HrManager hrManager = new HrManager(); + for (JsonAdaptedInterview jsonAdaptedInterview : interviews) { + Interview interview = jsonAdaptedInterview.toModelType(); + if (hrManager.hasInterview(interview)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_INTERVIEWS); + } + hrManager.addInterview(interview); + } + return hrManager; + } +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableHrManagerPositions.java b/src/main/java/seedu/address/storage/JsonSerializableHrManagerPositions.java new file mode 100644 index 00000000000..13e2f7be3fd --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonSerializableHrManagerPositions.java @@ -0,0 +1,60 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.HrManager; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.model.position.Position; + +/** + * An Immutable HrManager that is serializable to JSON format. + */ +@JsonRootName(value = "HrManagerPositions") +public class JsonSerializableHrManagerPositions { + public static final String MESSAGE_DUPLICATE_POSITIONS = "Positions list contains duplicate position(s)."; + + private final List<JsonAdaptedPosition> positions = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableHrManagerPositions} with the given positions. + */ + @JsonCreator + public JsonSerializableHrManagerPositions(@JsonProperty("positions") List<JsonAdaptedPosition> positions) { + this.positions.addAll(positions); + } + + /** + * Converts a given {@code HrManager} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableHrManagerPositions}. + */ + public JsonSerializableHrManagerPositions(ReadOnlyHrManager source) { + positions.addAll(source.getPositionList().stream().map(JsonAdaptedPosition::new) + .collect(Collectors.toList())); + + } + + /** + * Converts this serializable HrManagerPositions into the model's {@code HrManager} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public HrManager toModelType() throws IllegalValueException { + HrManager hrManager = new HrManager(); + for (JsonAdaptedPosition jsonAdaptedPosition : positions) { + Position position = jsonAdaptedPosition.toModelType(); + if (hrManager.hasPosition(position)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_POSITIONS); + } + hrManager.addPosition(position); + } + return hrManager; + } +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index beda8bd9f11..a5e95e1423e 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -5,14 +5,14 @@ import java.util.Optional; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyHrManager; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends HrManagerStorage, UserPrefsStorage { @Override Optional<UserPrefs> readUserPrefs() throws DataConversionException, IOException; @@ -21,12 +21,12 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; @Override - Path getAddressBookFilePath(); + Path getHrManagerCandidatesFilePath(); @Override - Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException; + Optional<ReadOnlyHrManager> readHrManager() throws DataConversionException, IOException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveHrManager(ReadOnlyHrManager hrManager) throws IOException; } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 79868290974..fed77d47743 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -7,25 +7,25 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyHrManager; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; /** - * Manages storage of AddressBook data in local storage. + * Manages storage of HrManager data in local storage. */ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; + private HrManagerStorage hrManagerStorage; private UserPrefsStorage userPrefsStorage; /** - * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. + * Creates a {@code StorageManager} with the given {@code HrManagerStorage} and {@code UserPrefStorage}. */ - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(HrManagerStorage hrManagerStorage, UserPrefsStorage userPrefsStorage) { super(); - this.addressBookStorage = addressBookStorage; + this.hrManagerStorage = hrManagerStorage; this.userPrefsStorage = userPrefsStorage; } @@ -47,33 +47,52 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { } - // ================ AddressBook methods ============================== + // ================ HrManager methods ============================== @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); + public Path getHrManagerCandidatesFilePath() { + return hrManagerStorage.getHrManagerCandidatesFilePath(); } @Override - public Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); + public Path getHrManagerPositionsFilePath() { + return hrManagerStorage.getHrManagerPositionsFilePath(); } @Override - public Optional<ReadOnlyAddressBook> readAddressBook(Path filePath) throws DataConversionException, IOException { - logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); + public Path getHrManagerInterviewsFilePath() { + return hrManagerStorage.getHrManagerInterviewsFilePath(); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); + public Optional<ReadOnlyHrManager> readHrManager() throws DataConversionException, IOException { + return readHrManager(hrManagerStorage.getHrManagerCandidatesFilePath(), + hrManagerStorage.getHrManagerPositionsFilePath(), hrManagerStorage.getHrManagerInterviewsFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); + public Optional<ReadOnlyHrManager> readHrManager(Path candidatesFilePath, Path positionsFilepath, + Path interviewsFilePath) + throws DataConversionException, IOException { + logger.fine("Attempting to read candidates data from file: " + candidatesFilePath); + logger.fine("Attempting to read positions data from file: " + positionsFilepath); + logger.fine("Attempting to read interviews data from file: " + interviewsFilePath); + return hrManagerStorage.readHrManager(candidatesFilePath, positionsFilepath, interviewsFilePath); } + @Override + public void saveHrManager(ReadOnlyHrManager hrManager) throws IOException { + saveHrManager(hrManager, hrManagerStorage.getHrManagerCandidatesFilePath(), + hrManagerStorage.getHrManagerPositionsFilePath(), hrManagerStorage.getHrManagerInterviewsFilePath()); + } + + @Override + public void saveHrManager(ReadOnlyHrManager hrManager, Path candidatesFilePath, Path positionsFilePath, + Path interviewsFilePath) + throws IOException { + logger.fine("Attempting to write to candidate data file: " + candidatesFilePath); + logger.fine("Attempting to write to position data file: " + positionsFilePath); + logger.fine("Attempting to write to interviews data file: " + interviewsFilePath); + hrManagerStorage.saveHrManager(hrManager, candidatesFilePath, positionsFilePath, interviewsFilePath); + } } diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..4d3c0f00807 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -4,8 +4,8 @@ import javafx.fxml.FXML; import javafx.scene.control.TextField; import javafx.scene.layout.Region; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; /** diff --git a/src/main/java/seedu/address/ui/DisplayListPanel.java b/src/main/java/seedu/address/ui/DisplayListPanel.java new file mode 100644 index 00000000000..0aa48608429 --- /dev/null +++ b/src/main/java/seedu/address/ui/DisplayListPanel.java @@ -0,0 +1,5 @@ +package seedu.address.ui; + +public interface DisplayListPanel<T> { + public T getRoot(); +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 9a665915949..99c59f526c8 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart<Stage> { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2122s1-cs2103t-w13-1.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/InterviewCard.java b/src/main/java/seedu/address/ui/InterviewCard.java new file mode 100644 index 00000000000..9a9e05d828c --- /dev/null +++ b/src/main/java/seedu/address/ui/InterviewCard.java @@ -0,0 +1,100 @@ +package seedu.address.ui; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +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.interview.Interview; +import seedu.address.model.person.Person; + +/** + * A UI component that displays information of a {@code Interview}. + */ +public class InterviewCard extends UiPart<Region> { + + private static final String FXML = "InterviewListCard.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 <a href="https://github.com/se-edu/addressbook-level4/issues/336">The issue on AddressBook level 4</a> + */ + + private final Interview interview; + + @FXML + private HBox cardPane; + @FXML + private Label id; + /** + * status uses the FXML tags format + */ + @FXML + private FlowPane tags; + @FXML + private Label position; + @FXML + private Label time; + @FXML + private Label date; + @FXML + private Label duration; + @FXML + private FlowPane candidates; + + /** + * Creates a {@code InterviewCard} with the given {@code Interview} and index to display. + */ + public InterviewCard(Interview interview, int displayedIndex) { + super(FXML); + this.interview = interview; + id.setText(displayedIndex + ". "); + position.setText(interview.getPositionTitle().fullTitle); + date.setText("Date: " + interview.getDisplayDate()); + time.setText("Time: " + interview.getDisplayTimePeriod()); + duration.setText("Duration: " + interview.getDuration().toMinutes() + " min"); + Interview.InterviewStatus status = interview.getStatus(); + tags.getChildren().add(new Label(status.name())); + + List<Person> processedCandidates = interview.getCandidates().stream() + .sorted(Comparator.comparing(candidate -> candidate.getName().fullName)).collect(Collectors.toList()); + + for (int i = 0; i < processedCandidates.size(); i++) { + Person c = processedCandidates.get(i); + if (i != processedCandidates.size() - 1) { + candidates.getChildren().add(new Label(c.getName().fullName + " | ")); + } else { + candidates.getChildren().add(new Label(c.getName().fullName)); + } + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof InterviewCard)) { + return false; + } + + // state check + InterviewCard card = (InterviewCard) other; + return id.getText().equals(card.id.getText()) + && position.equals(card.position) + && time.equals(card.time) + && date.equals(card.date) + && duration.equals(card.duration) + && tags.equals(card.tags); + } +} diff --git a/src/main/java/seedu/address/ui/InterviewListPanel.java b/src/main/java/seedu/address/ui/InterviewListPanel.java new file mode 100644 index 00000000000..1476ef4dcd5 --- /dev/null +++ b/src/main/java/seedu/address/ui/InterviewListPanel.java @@ -0,0 +1,50 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.Node; +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.interview.Interview; + +/** + * Panel containing the list of interviews. + */ +public class InterviewListPanel extends UiPart<Region> implements DisplayListPanel<Node> { + private static final String FXML = "InterviewListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(InterviewListPanel.class); + + @FXML + private ListView<Interview> interviewListView; + + /** + * Creates a {@code InterviewListPanel} with the given {@code ObservableList}. + */ + public InterviewListPanel(ObservableList<Interview> interviewList) { + super(FXML); + interviewListView.setItems(interviewList); + interviewListView.setCellFactory(listView -> new InterviewListPanel.InterviewListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Interview} using a {@code InterviewCard}. + */ + class InterviewListViewCell extends ListCell<Interview> { + + @Override + protected void updateItem(Interview interview, boolean empty) { + super.updateItem(interview, empty); + + if (empty || interview == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new InterviewCard(interview, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..376ba54c94a 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -4,6 +4,7 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; @@ -12,9 +13,9 @@ import javafx.stage.Stage; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.CommandResult; import seedu.address.logic.Logic; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.candidate.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -32,6 +33,8 @@ public class MainWindow extends UiPart<Stage> { // Independent Ui parts residing in this Ui container private PersonListPanel personListPanel; + private PositionListPanel positionListPanel; + private InterviewListPanel interviewListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -41,9 +44,24 @@ public class MainWindow extends UiPart<Stage> { @FXML private MenuItem helpMenuItem; + @FXML + private Label personListLabel; + + @FXML + private Label positionListLabel; + + @FXML + private Label interviewListLabel; + @FXML private StackPane personListPanelPlaceholder; + @FXML + private StackPane positionListPanelPlaceholder; + + @FXML + private StackPane interviewListPanelPlaceholder; + @FXML private StackPane resultDisplayPlaceholder; @@ -78,6 +96,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -113,14 +132,24 @@ void fillInnerParts() { personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + positionListPanel = new PositionListPanel(logic.getFilteredPositionList()); + positionListPanelPlaceholder.getChildren().add(positionListPanel.getRoot()); + + interviewListPanel = new InterviewListPanel(logic.getFilteredInterviewList()); + interviewListPanelPlaceholder.getChildren().add(interviewListPanel.getRoot()); + resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getHrManagerCandidatesFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + personListLabel.setText("Candidates"); + positionListLabel.setText("Positions"); + interviewListLabel.setText("Interviews"); } /** @@ -163,8 +192,96 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + private void handleListC() { + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getHrManagerCandidatesFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + + personListLabel.setText("Candidates"); + } + + private void handleListP() { + positionListPanel = new PositionListPanel(logic.getFilteredPositionList()); + positionListPanelPlaceholder.getChildren().add(positionListPanel.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getHrManagerPositionsFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + positionListLabel.setText("Positions"); + } + + private void handleListI() { + interviewListPanel = new InterviewListPanel(logic.getFilteredInterviewList()); + interviewListPanelPlaceholder.getChildren().add(interviewListPanel.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getHrManagerInterviewsFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + interviewListLabel.setText("Interviews"); + } + + private void handleFindC() { + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getHrManagerCandidatesFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + + personListLabel.setText("Candidates (filtered)"); + } + + private void handleFindP() { + positionListPanel = new PositionListPanel(logic.getFilteredPositionList()); + positionListPanelPlaceholder.getChildren().add(positionListPanel.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getHrManagerPositionsFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + positionListLabel.setText("Positions (filtered)"); + } + + private void handleFindI() { + interviewListPanel = new InterviewListPanel(logic.getFilteredInterviewList()); + interviewListPanelPlaceholder.getChildren().add(interviewListPanel.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getHrManagerInterviewsFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + interviewListLabel.setText("Interviews (filtered)"); + } + + private void handleC() { //Update person and interview lists + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + + interviewListPanel = new InterviewListPanel(logic.getFilteredInterviewList()); + interviewListPanelPlaceholder.getChildren().add(interviewListPanel.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getHrManagerInterviewsFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + } + + private void handleP() { //Update person and position lists + positionListPanel = new PositionListPanel(logic.getFilteredPositionList()); + positionListPanelPlaceholder.getChildren().add(positionListPanel.getRoot()); + + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + + interviewListPanel = new InterviewListPanel(logic.getFilteredInterviewList()); + interviewListPanelPlaceholder.getChildren().add(interviewListPanel.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getHrManagerInterviewsFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + } + + private void handleI() { //Update person and interview lists + interviewListPanel = new InterviewListPanel(logic.getFilteredInterviewList()); + interviewListPanelPlaceholder.getChildren().add(interviewListPanel.getRoot()); + + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getHrManagerInterviewsFilePath()); + statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); } /** @@ -178,12 +295,41 @@ private CommandResult executeCommand(String commandText) throws CommandException logger.info("Result: " + commandResult.getFeedbackToUser()); resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); - if (commandResult.isShowHelp()) { + switch(commandResult.getCommandType()) { + case HELP: handleHelp(); - } - - if (commandResult.isExit()) { + break; + case EXIT: handleExit(); + break; + case LIST_C: + handleListC(); + break; + case LIST_P: + handleListP(); + break; + case LIST_I: + handleListI(); + break; + case FIND_C: + handleFindC(); + break; + case FIND_P: + handleFindP(); + break; + case FIND_I: + handleFindI(); + break; + case CANDIDATE: + handleC(); + break; + case POSITION: + handleP(); + break; + case INTERVIEW: + handleI(); + break; + default: } return commandResult; diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 7fc927bc5d9..401d109ab93 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -7,10 +7,11 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; +import seedu.address.model.interview.Interview; import seedu.address.model.person.Person; /** - * An UI component that displays information of a {@code Person}. + * A UI component that displays information of a {@code Person}. */ public class PersonCard extends UiPart<Region> { @@ -40,6 +41,14 @@ public class PersonCard extends UiPart<Region> { private Label email; @FXML private FlowPane tags; + @FXML + private Label remark; + @FXML + private Label status; + @FXML + private FlowPane positions; + @FXML + private Label interviews; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. @@ -52,9 +61,34 @@ public PersonCard(Person person, int displayedIndex) { phone.setText(person.getPhone().value); address.setText(person.getAddress().value); email.setText(person.getEmail().value); + remark.setText(person.getRemark().value); + status.setText(person.getStatus().toString()); + person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + person.getPositions().stream() + .sorted(Comparator.comparing(position -> position.getTitle().fullTitle)) + .forEach(position -> positions.getChildren().add(new Label(position.getTitle().fullTitle + " "))); + + //instead of individual labels with wrap text, create one label with all interviews and then wrap + StringBuilder stringBuilder = new StringBuilder(); + person.getInterviews().stream() + .sorted(Comparator.comparing(Interview::getDate)) + .forEach(interview -> { + String temp = interview.getDisplayStringWithoutNames(); + for (int i = 0; i < temp.length(); i += 60) { + if (i + 60 < temp.length()) { + stringBuilder.append(temp.substring(i, i + 60) + "\n"); + } else { + stringBuilder.append(temp.substring(i) + "\n"); + } + } + }); + if (!stringBuilder.toString().equals("")) { + interviews.setText(stringBuilder.toString()); + interviews.setStyle("-fx-text-fill: khaki;"); + } } @Override diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index f4c501a897b..3789a215334 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -4,6 +4,7 @@ import javafx.collections.ObservableList; import javafx.fxml.FXML; +import javafx.scene.Node; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.layout.Region; @@ -13,7 +14,7 @@ /** * Panel containing the list of persons. */ -public class PersonListPanel extends UiPart<Region> { +public class PersonListPanel extends UiPart<Region> implements DisplayListPanel<Node> { private static final String FXML = "PersonListPanel.fxml"; private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); diff --git a/src/main/java/seedu/address/ui/PositionCard.java b/src/main/java/seedu/address/ui/PositionCard.java new file mode 100644 index 00000000000..2af972bf6ff --- /dev/null +++ b/src/main/java/seedu/address/ui/PositionCard.java @@ -0,0 +1,69 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.position.Position; + +/** + * A UI component that displays information of a {@code Position}. + */ +public class PositionCard extends UiPart<Region> { + + private static final String FXML = "PositionListCard.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 <a href="https://github.com/se-edu/addressbook-level4/issues/336">The issue on AddressBook level 4</a> + */ + + public final Position position; + + @FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label id; + + /** status uses the FXML tags format */ + @FXML + private FlowPane tags; + + /** + * Creates a {@code PositionCode} with the given {@code Position} and index to display. + */ + public PositionCard(Position position, int displayedIndex) { + super(FXML); + this.position = position; + id.setText(displayedIndex + ". "); + title.setText(position.getTitle().fullTitle); + + Position.PositionStatus status = position.getStatus(); + this.tags.getChildren().add(new Label(status.name())); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PositionCard)) { + return false; + } + + // state check + PositionCard card = (PositionCard) other; + return id.getText().equals(card.id.getText()) + && position.equals(card.position); + } +} + diff --git a/src/main/java/seedu/address/ui/PositionListPanel.java b/src/main/java/seedu/address/ui/PositionListPanel.java new file mode 100644 index 00000000000..0a209448d86 --- /dev/null +++ b/src/main/java/seedu/address/ui/PositionListPanel.java @@ -0,0 +1,50 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.Node; +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.position.Position; + +/** + * Panel containing the list of persons. + */ +public class PositionListPanel extends UiPart<Region> implements DisplayListPanel<Node> { + private static final String FXML = "PositionListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PositionListPanel.class); + + @FXML + private ListView<Position> positionListView; + + /** + * Creates a {@code PositionListPanel} with the given {@code ObservableList}. + */ + public PositionListPanel(ObservableList<Position> positionList) { + super(FXML); + positionListView.setItems(positionList); + positionListView.setCellFactory(listView -> new PositionListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Position} using a {@code PositionCard}. + */ + class PositionListViewCell extends ListCell<Position> { + @Override + protected void updateItem(Position position, boolean empty) { + super.updateItem(position, empty); + + if (empty || position == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PositionCard(position, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/ResultDisplay.java index 7d98e84eedf..b8a87f550d6 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/address/ui/ResultDisplay.java @@ -23,6 +23,7 @@ public ResultDisplay() { public void setFeedbackToUser(String feedbackToUser) { requireNonNull(feedbackToUser); resultDisplay.setText(feedbackToUser); + resultDisplay.setWrapText(true); } } diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 9ce9bcfb569..57ac0ab489a 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -24,6 +24,12 @@ -fx-opacity: 1; } +#person-interview-text { + -fx-font-size: 11pt; + -fx-font-family: "Open Sans Semibold"; + -fx-fill: #f5f5f7; +} + .text-field { -fx-font-size: 12pt; -fx-font-family: "Open Sans Semibold"; @@ -120,6 +126,12 @@ -fx-text-fill: white; } +.list_label { + -fx-font-family: "Open Sans Semibold"; + -fx-font-size: 20px; + -fx-text-fill: #ffffff; +} + .cell_big_label { -fx-font-family: "Open Sans Semibold"; -fx-font-size: 16px; @@ -350,3 +362,31 @@ -fx-background-radius: 2; -fx-font-size: 11; } + +#positions { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#positions .label { + -fx-text-fill: white; + -fx-background-color: #8e9294; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + +#status { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#status .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: 11; +} diff --git a/src/main/resources/view/InterviewListCard.fxml b/src/main/resources/view/InterviewListCard.fxml new file mode 100644 index 00000000000..f2026dac398 --- /dev/null +++ b/src/main/resources/view/InterviewListCard.fxml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.FlowPane?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.VBox?> + +<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <GridPane HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150"/> + </columnConstraints> + <VBox alignment="CENTER_LEFT" minHeight="105" GridPane.columnIndex="0"> + <padding> + <Insets top="5" right="5" bottom="5" left="15"/> + </padding> + <HBox spacing="5" alignment="CENTER_LEFT"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE"/> + </minWidth> + </Label> + <Label fx:id="position" text="\$position" styleClass="cell_big_label" wrapText="true"/> + </HBox> + <FlowPane fx:id="tags"/> + <Label fx:id="date" styleClass="cell_small_label" text="\$date"/> + <Label fx:id="time" styleClass="cell_small_label" text="\$startTime"/> + <Label fx:id="duration" styleClass="cell_small_label" text="\$duration"/> + <FlowPane fx:id="candidates"/> + </VBox> + </GridPane> +</HBox> diff --git a/src/main/resources/view/InterviewListPanel.fxml b/src/main/resources/view/InterviewListPanel.fxml new file mode 100644 index 00000000000..e10d1f3dad7 --- /dev/null +++ b/src/main/resources/view/InterviewListPanel.fxml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> + + +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="interviewListView" VBox.vgrow="ALWAYS" /> +</VBox> diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 32bcf2c8e70..634803bbef8 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -6,13 +6,14 @@ <?import javafx.scene.control.Menu?> <?import javafx.scene.control.MenuBar?> <?import javafx.scene.control.MenuItem?> -<?import javafx.scene.control.SplitPane?> <?import javafx.scene.image.Image?> <?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.VBox?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.HBox?> <fx:root type="javafx.stage.Stage" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" - title="Address App" minWidth="450" minHeight="600" onCloseRequest="#handleExit"> + title="HR Manager" minWidth="450" minHeight="600" onCloseRequest="#handleExit"> <icons> <Image url="@/images/address_book_32.png" /> </icons> @@ -20,42 +21,56 @@ <Scene> <stylesheets> <URL value="@Fonts.css"/> - <URL value="@DarkTheme.css" /> + <URL value="@NewTheme.css" /> <URL value="@Extensions.css" /> </stylesheets> + <VBox> + <MenuBar fx:id="menuBar" VBox.vgrow="NEVER"> + <Menu mnemonicParsing="false" text="File"> + <MenuItem mnemonicParsing="false" onAction="#handleExit" text="Exit"/> + </Menu> + <Menu mnemonicParsing="false" text="Help"> + <MenuItem fx:id="helpMenuItem" mnemonicParsing="false" onAction="#handleHelp" text="Help"/> + </Menu> + </MenuBar> - <VBox> - <MenuBar fx:id="menuBar" VBox.vgrow="NEVER"> - <Menu mnemonicParsing="false" text="File"> - <MenuItem mnemonicParsing="false" onAction="#handleExit" text="Exit" /> - </Menu> - <Menu mnemonicParsing="false" text="Help"> - <MenuItem fx:id="helpMenuItem" mnemonicParsing="false" onAction="#handleHelp" text="Help" /> - </Menu> - </MenuBar> + <StackPane VBox.vgrow="NEVER" fx:id="commandBoxPlaceholder" styleClass="pane-with-border"> + <padding> + <Insets top="5" right="10" bottom="5" left="10"/> + </padding> + </StackPane> - <StackPane VBox.vgrow="NEVER" fx:id="commandBoxPlaceholder" styleClass="pane-with-border"> - <padding> - <Insets top="5" right="10" bottom="5" left="10" /> - </padding> - </StackPane> - - <StackPane VBox.vgrow="NEVER" fx:id="resultDisplayPlaceholder" styleClass="pane-with-border" - minHeight="100" prefHeight="100" maxHeight="100"> - <padding> - <Insets top="5" right="10" bottom="5" left="10" /> - </padding> - </StackPane> - - <VBox fx:id="personList" styleClass="pane-with-border" minWidth="340" prefWidth="340" VBox.vgrow="ALWAYS"> - <padding> - <Insets top="10" right="10" bottom="10" left="10" /> - </padding> - <StackPane fx:id="personListPanelPlaceholder" VBox.vgrow="ALWAYS"/> - </VBox> - - <StackPane fx:id="statusbarPlaceholder" VBox.vgrow="NEVER" /> - </VBox> - </Scene> - </scene> + <StackPane VBox.vgrow="NEVER" fx:id="resultDisplayPlaceholder" styleClass="pane-with-border" + minHeight="100" prefHeight="100" maxHeight="100"> + <padding> + <Insets top="5" right="10" bottom="5" left="10"/> + </padding> + </StackPane> + <HBox fx:id="displayList" alignment="CENTER" VBox.vgrow="ALWAYS"> + <VBox fx:id="personList" styleClass="pane-with-border" HBox.hgrow="ALWAYS" VBox.vgrow="ALWAYS"> + <padding> + <Insets top="10" right="10" bottom="10" left="10"/> + </padding> + <Label fx:id="personListLabel" styleClass="list_label"/> + <StackPane fx:id="personListPanelPlaceholder" VBox.vgrow="ALWAYS"/> + </VBox> + <VBox fx:id="positionList" styleClass="pane-with-border" HBox.hgrow="ALWAYS" VBox.vgrow="ALWAYS"> + <padding> + <Insets top="10" right="10" bottom="10" left="10"/> + </padding> + <Label fx:id="positionListLabel" styleClass="list_label"/> + <StackPane fx:id="positionListPanelPlaceholder" VBox.vgrow="ALWAYS"/> + </VBox> + <VBox fx:id="interviewList" styleClass="pane-with-border" HBox.hgrow="ALWAYS" VBox.vgrow="ALWAYS"> + <padding> + <Insets top="10" right="10" bottom="10" left="10"/> + </padding> + <Label fx:id="interviewListLabel" styleClass="list_label"/> + <StackPane fx:id="interviewListPanelPlaceholder" VBox.vgrow="ALWAYS"/> + </VBox> + </HBox> + <StackPane fx:id="statusbarPlaceholder" VBox.vgrow="NEVER"/> + </VBox> + </Scene> + </scene> </fx:root> diff --git a/src/main/resources/view/NewTheme.css b/src/main/resources/view/NewTheme.css new file mode 100644 index 00000000000..671de06d7ea --- /dev/null +++ b/src/main/resources/view/NewTheme.css @@ -0,0 +1,392 @@ +.background { + -fx-background-color: derive(#1d1d1d, 20%); + background-color: #383838; /* Used in the default.html file */ +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Open Sans Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Open Sans Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Open Sans Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +#person-interview-text { + -fx-font-size: 11pt; + -fx-font-family: "Open Sans Semibold"; + -fx-fill: #f5f5f7; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Open Sans Semibold"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: #1d1d1d; + -fx-background-color: #1d1d1d; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Open Sans Light"; + -fx-text-fill: white; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: transparent transparent transparent #4d4d4d; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #3f3839; +} + +.list-cell:filled:odd { + -fx-background-color: #413234; +} + +.list-cell:filled:selected { + -fx-background-color: #342d2e +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #AB6D23; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: white; +} + +.list_label { + -fx-font-family: "Open Sans Semibold"; + -fx-font-size: 20px; + -fx-text-fill: #F78812; +} + +.cell_big_label { + -fx-font-family: "Open Sans Semibold"; + -fx-font-size: 16px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Open Sans Regular"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + +.stack-pane { + -fx-background-color: derive(#1d1d1d, 20%); +} + +.pane-with-border { + -fx-background-color: derive(#303030, 10%); + -fx-border-color: transparent; + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#303030, 30%); +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Open Sans Light"; + -fx-font-size: 13pt; + -fx-text-fill: white; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Open Sans Light"; + -fx-text-fill: white; + -fx-padding: 4px; + -fx-pref-height: 30px; +} + +.status-bar-with-border { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: white; +} + +.grid-pane { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 30%); + -fx-border-width: 1px; +} + +.grid-pane .stack-pane { + -fx-background-color: derive(#1d1d1d, 30%); +} + +.context-menu { + -fx-background-color: derive(#1d1d1d, 50%); +} + +.context-menu .label { + -fx-text-fill: white; +} + +.menu-bar { + -fx-background-color: derive(#303030, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Open Sans Light"; + -fx-text-fill: white; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Open Sans Regular"; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: white; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#1d1d1d, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: white; + -fx-text-fill: white; +} + +.scroll-bar { + -fx-background-color: derive(#2a2a2a, 20%); +} + +.scroll-bar .thumb { + -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: #545454; + -fx-background-insets: 0; + -fx-border-color: #303030; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Open Sans Light"; + -fx-font-size: 13pt; + -fx-text-fill: white; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: #545454; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .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: 11; +} + +#positions { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#positions .label { + -fx-text-fill: white; + -fx-background-color: #8e9294; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + +#status { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#status .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: 11; +} diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad55..17adcbfeb56 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -10,27 +10,31 @@ <?import javafx.scene.layout.VBox?> <HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> - <GridPane HBox.hgrow="ALWAYS"> - <columnConstraints> - <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> - </columnConstraints> - <VBox alignment="CENTER_LEFT" minHeight="105" GridPane.columnIndex="0"> - <padding> - <Insets top="5" right="5" bottom="5" left="15" /> - </padding> - <HBox spacing="5" alignment="CENTER_LEFT"> - <Label fx:id="id" styleClass="cell_big_label"> - <minWidth> - <!-- Ensures that the label text is never truncated --> - <Region fx:constant="USE_PREF_SIZE" /> - </minWidth> - </Label> - <Label fx:id="name" text="\$first" styleClass="cell_big_label" /> - </HBox> - <FlowPane fx:id="tags" /> - <Label fx:id="phone" styleClass="cell_small_label" text="\$phone" /> - <Label fx:id="address" styleClass="cell_small_label" text="\$address" /> - <Label fx:id="email" styleClass="cell_small_label" text="\$email" /> - </VBox> - </GridPane> + <GridPane HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150"/> + </columnConstraints> + <VBox alignment="CENTER_LEFT" minHeight="105" GridPane.columnIndex="0"> + <padding> + <Insets top="5" right="5" bottom="5" left="15"/> + </padding> + <HBox spacing="5" alignment="CENTER_LEFT"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE"/> + </minWidth> + </Label> + <Label fx:id="name" text="\$first" styleClass="cell_big_label" wrapText="true"/> + </HBox> + <FlowPane fx:id="tags"/> + <FlowPane fx:id="positions"/> + <Label fx:id="interviews" wrapText="true"/> + <Label fx:id="phone" styleClass="cell_small_label" text="\$phone" wrapText="true"/> + <Label fx:id="address" styleClass="cell_small_label" text="\$address" wrapText="true"/> + <Label fx:id="email" styleClass="cell_small_label" text="\$email" wrapText="true"/> + <Label fx:id="remark" styleClass="cell_small_label" text="\$remark" wrapText="true"/> + <Label fx:id="status" styleClass="cell_small_label" text="\$status" wrapText="true"/> + </VBox> + </GridPane> </HBox> diff --git a/src/main/resources/view/PositionListCard.fxml b/src/main/resources/view/PositionListCard.fxml new file mode 100644 index 00000000000..98ac9734464 --- /dev/null +++ b/src/main/resources/view/PositionListCard.fxml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.FlowPane?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.VBox?> + +<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <GridPane HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150"/> + </columnConstraints> + <VBox alignment="CENTER_LEFT" minHeight="105" GridPane.columnIndex="0"> + <padding> + <Insets top="5" right="5" bottom="5" left="15"/> + </padding> + <HBox spacing="5" alignment="CENTER_LEFT"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE"/> + </minWidth> + </Label> + <Label fx:id="title" styleClass="cell_big_label" text="\$title" wrapText="true"/> + </HBox> + <FlowPane fx:id="tags"> + <padding> + <Insets left="20"/> + </padding> + </FlowPane> + </VBox> + </GridPane> +</HBox> diff --git a/src/main/resources/view/PositionListPanel.fxml b/src/main/resources/view/PositionListPanel.fxml new file mode 100644 index 00000000000..763924217cf --- /dev/null +++ b/src/main/resources/view/PositionListPanel.fxml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> + + +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="positionListView" VBox.vgrow="ALWAYS" /> +</VBox> diff --git a/src/test/data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json b/src/test/data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json deleted file mode 100644 index a1097343b5d..00000000000 --- a/src/test/data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json +++ /dev/null @@ -1 +0,0 @@ -not json format! diff --git a/src/test/data/JsonHrManagerStorageTest/invalidAndValidInterviewHrManager.json b/src/test/data/JsonHrManagerStorageTest/invalidAndValidInterviewHrManager.json new file mode 100644 index 00000000000..72099237a85 --- /dev/null +++ b/src/test/data/JsonHrManagerStorageTest/invalidAndValidInterviewHrManager.json @@ -0,0 +1,17 @@ +{ + "interviews" : [ { + "position" : "Accountant", + "candidateIDs" : [ "-550871537", "-2024498055" ], + "date" : "15/10/2021", + "startTime" : "1400", + "duration" : "120", + "status" : "PENDING" + }, { + "position" : "Invalid position acc-Manager", + "candidateIDs" : [ "-550871537", "-2024498055", "-1834523193" ], + "date" : "2021/10/15", + "startTime" : "14:00", + "duration" : "PT1H20M", + "status" : "PENDING" + } ] +} diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonHrManagerStorageTest/invalidAndValidPersonHrManager.json similarity index 61% rename from src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json rename to src/test/data/JsonHrManagerStorageTest/invalidAndValidPersonHrManager.json index 6a4d2b7181c..932a1413ef4 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json +++ b/src/test/data/JsonHrManagerStorageTest/invalidAndValidPersonHrManager.json @@ -3,11 +3,15 @@ "name": "Valid Person", "phone": "9482424", "email": "hans@example.com", - "address": "4th street" + "address": "4th street", + "remark": "", + "status": "applied" }, { "name": "Person With Invalid Phone Field", "phone": "948asdf2424", "email": "hans@example.com", - "address": "4th street" + "address": "4th street", + "remark": "", + "status": "applied" } ] } diff --git a/src/test/data/JsonHrManagerStorageTest/invalidAndValidPositionHrManager.json b/src/test/data/JsonHrManagerStorageTest/invalidAndValidPositionHrManager.json new file mode 100644 index 00000000000..b5645164483 --- /dev/null +++ b/src/test/data/JsonHrManagerStorageTest/invalidAndValidPositionHrManager.json @@ -0,0 +1,9 @@ +{ + "positions" : [ { + "title" : "invalid position acc-manager", + "positionStatus" : "OPEN" + }, { + "title" : "valid position account manager", + "positionStatus" : "OPEN" + } ] +} diff --git a/src/test/data/JsonHrManagerStorageTest/invalidInterviewHrManager.json b/src/test/data/JsonHrManagerStorageTest/invalidInterviewHrManager.json new file mode 100644 index 00000000000..5260dd97468 --- /dev/null +++ b/src/test/data/JsonHrManagerStorageTest/invalidInterviewHrManager.json @@ -0,0 +1,10 @@ +{ + "interviews" : [ { + "position" : "Invalid position acc-Manager", + "candidateIDs" : [ "-550871537", "-2024498055", "-1834523193" ], + "date" : "2021/10/15", + "startTime" : "14:00", + "duration" : "PT1H20M", + "status" : "PENDING" + } ] +} diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonHrManagerStorageTest/invalidPersonHrManager.json similarity index 100% rename from src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json rename to src/test/data/JsonHrManagerStorageTest/invalidPersonHrManager.json diff --git a/src/test/data/JsonHrManagerStorageTest/invalidPositionHrManager.json b/src/test/data/JsonHrManagerStorageTest/invalidPositionHrManager.json new file mode 100644 index 00000000000..31ce6fde032 --- /dev/null +++ b/src/test/data/JsonHrManagerStorageTest/invalidPositionHrManager.json @@ -0,0 +1,6 @@ +{ + "positions" : [ { + "title" : "invalid position acc-manager", + "positionStatus" : "OPEN" + } ] +} diff --git a/src/test/data/JsonHrManagerStorageTest/notJsonFormatHrManagerCandidates.json b/src/test/data/JsonHrManagerStorageTest/notJsonFormatHrManagerCandidates.json new file mode 100644 index 00000000000..2eea3f097cc --- /dev/null +++ b/src/test/data/JsonHrManagerStorageTest/notJsonFormatHrManagerCandidates.json @@ -0,0 +1 @@ +not json format for candidates! diff --git a/src/test/data/JsonHrManagerStorageTest/notJsonFormatHrManagerInterviews.json b/src/test/data/JsonHrManagerStorageTest/notJsonFormatHrManagerInterviews.json new file mode 100644 index 00000000000..a87c469dfb0 --- /dev/null +++ b/src/test/data/JsonHrManagerStorageTest/notJsonFormatHrManagerInterviews.json @@ -0,0 +1 @@ +not json format for interviews! diff --git a/src/test/data/JsonHrManagerStorageTest/notJsonFormatHrManagerPositions.json b/src/test/data/JsonHrManagerStorageTest/notJsonFormatHrManagerPositions.json new file mode 100644 index 00000000000..db65da2973a --- /dev/null +++ b/src/test/data/JsonHrManagerStorageTest/notJsonFormatHrManagerPositions.json @@ -0,0 +1 @@ +not json format for positions! diff --git a/src/test/data/JsonHrManagerStorageTest/validInterviewHrManager.json b/src/test/data/JsonHrManagerStorageTest/validInterviewHrManager.json new file mode 100644 index 00000000000..1262e5deff6 --- /dev/null +++ b/src/test/data/JsonHrManagerStorageTest/validInterviewHrManager.json @@ -0,0 +1,10 @@ +{ + "interviews" : [ { + "position" : "Valid position", + "candidateIDs" : [ "-550871537", "-2024498055", "-1834523193" ], + "date" : "15/10/2021", + "startTime" : "1400", + "duration" : "120", + "status" : "PENDING" + } ] +} diff --git a/src/test/data/JsonHrManagerStorageTest/validPersonHrManager.json b/src/test/data/JsonHrManagerStorageTest/validPersonHrManager.json new file mode 100644 index 00000000000..2fb17ef3af2 --- /dev/null +++ b/src/test/data/JsonHrManagerStorageTest/validPersonHrManager.json @@ -0,0 +1,8 @@ +{ + "persons": [ { + "name": "Person with valid name", + "phone": "9482424", + "email": "hans@example.com", + "address": "4th street" + } ] +} diff --git a/src/test/data/JsonHrManagerStorageTest/validPositionHrManager.json b/src/test/data/JsonHrManagerStorageTest/validPositionHrManager.json new file mode 100644 index 00000000000..65481d6281a --- /dev/null +++ b/src/test/data/JsonHrManagerStorageTest/validPositionHrManager.json @@ -0,0 +1,6 @@ +{ + "positions" : [ { + "title" : "valid position account manager", + "positionStatus" : "OPEN" + } ] +} diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json deleted file mode 100644 index f10eddee12e..00000000000 --- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "_comment": "AddressBook save file which contains the same Person values as in TypicalPersons#getTypicalAddressBook()", - "persons" : [ { - "name" : "Alice Pauline", - "phone" : "94351253", - "email" : "alice@example.com", - "address" : "123, Jurong West Ave 6, #08-111", - "tagged" : [ "friends" ] - }, { - "name" : "Benson Meier", - "phone" : "98765432", - "email" : "johnd@example.com", - "address" : "311, Clementi Ave 2, #02-25", - "tagged" : [ "owesMoney", "friends" ] - }, { - "name" : "Carl Kurz", - "phone" : "95352563", - "email" : "heinz@example.com", - "address" : "wall street", - "tagged" : [ ] - }, { - "name" : "Daniel Meier", - "phone" : "87652533", - "email" : "cornelia@example.com", - "address" : "10th street", - "tagged" : [ "friends" ] - }, { - "name" : "Elle Meyer", - "phone" : "9482224", - "email" : "werner@example.com", - "address" : "michegan ave", - "tagged" : [ ] - }, { - "name" : "Fiona Kunz", - "phone" : "9482427", - "email" : "lydia@example.com", - "address" : "little tokyo", - "tagged" : [ ] - }, { - "name" : "George Best", - "phone" : "9482442", - "email" : "anna@example.com", - "address" : "4th street", - "tagged" : [ ] - } ] -} diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableHrManagerCandidatesTest/duplicatePersonHrManager.json similarity index 55% rename from src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json rename to src/test/data/JsonSerializableHrManagerCandidatesTest/duplicatePersonHrManager.json index 48831cc7674..7488658c77f 100644 --- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json +++ b/src/test/data/JsonSerializableHrManagerCandidatesTest/duplicatePersonHrManager.json @@ -4,11 +4,15 @@ "phone": "94351253", "email": "alice@example.com", "address": "123, Jurong West Ave 6, #08-111", - "tagged": [ "friends" ] + "remark" : "", + "tagged": [ "friends" ], + "status": "Applied" }, { "name": "Alice Pauline", "phone": "94351253", - "email": "pauline@example.com", - "address": "4th street" + "email": "alice@example.com", + "address": "4th street", + "remark" : "", + "status": "Applied" } ] } diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableHrManagerCandidatesTest/invalidPersonHrManager.json similarity index 100% rename from src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json rename to src/test/data/JsonSerializableHrManagerCandidatesTest/invalidPersonHrManager.json diff --git a/src/test/data/JsonSerializableHrManagerCandidatesTest/typicalPersonsHrManager.json b/src/test/data/JsonSerializableHrManagerCandidatesTest/typicalPersonsHrManager.json new file mode 100644 index 00000000000..de67b574dc9 --- /dev/null +++ b/src/test/data/JsonSerializableHrManagerCandidatesTest/typicalPersonsHrManager.json @@ -0,0 +1,88 @@ +{ + "_comment": "HrManager save file which contains the same Person values as in TypicalPersons#getTypicalPersons()", + "persons" : [ { + "name" : "Alice Pauline", + "phone" : "94351253", + "email" : "alice@example.com", + "address" : "123, Jurong West Ave 6, #08-111", + "remark" : "She likes aardvarks.", + "tagged" : [ "friends" ], + "status": "Applied", + "positions" : [ { + "title" : "Accountant", + "positionStatus" : "OPEN" + } ] + }, { + "name" : "Benson Meier", + "phone" : "98765432", + "email" : "johnd@example.com", + "address" : "311, Clementi Ave 2, #02-25", + "remark" : "He can't handle beer!", + "tagged" : [ "owesMoney", "friends" ], + "status": "Scheduled", + "positions" : [ { + "title" : "HR Manager", + "positionStatus" : "OPEN" + } ] + }, { + "name" : "Carl Kurz", + "phone" : "95352563", + "email" : "heinz@example.com", + "address" : "wall street", + "remark" : "", + "tagged" : [ ], + "status": "Rejected", + "positions" : [ { + "title" : "HR Manager", + "positionStatus" : "OPEN" + } ] + }, { + "name" : "Daniel Meier", + "phone" : "87652533", + "email" : "cornelia@example.com", + "address" : "10th street", + "remark" : "", + "tagged" : [ "friends" ], + "status": "Scheduled", + "positions" : [ { + "title" : "HR Manager", + "positionStatus" : "OPEN" + } ] + }, { + "name" : "Elle Meyer", + "phone" : "9482224", + "email" : "werner@example.com", + "address" : "michegan ave", + "remark" : "", + "tagged" : [ ], + "status": "Withdrawn", + "positions" : [ { + "title" : "HR Manager", + "positionStatus" : "OPEN" + } ] + }, { + "name" : "Fiona Kunz", + "phone" : "9482427", + "email" : "lydia@example.com", + "address" : "little tokyo", + "remark" : "", + "tagged" : [ ], + "status": "Applied", + "positions" : [ { + "title" : "HR Manager", + "positionStatus" : "OPEN" + } ] + }, { + "name" : "George Best", + "phone" : "9482442", + "email" : "anna@example.com", + "address" : "4th street", + "remark" : "", + "tagged" : [ ], + "status": "Scheduled", + "positions" : [ { + "title" : "HR Manager", + "positionStatus" : "OPEN" + } ] + } ] +} diff --git a/src/test/data/JsonSerializableHrManagerInterviewsTest/duplicateInterviewHrManager.json b/src/test/data/JsonSerializableHrManagerInterviewsTest/duplicateInterviewHrManager.json new file mode 100644 index 00000000000..4486ea5c48d --- /dev/null +++ b/src/test/data/JsonSerializableHrManagerInterviewsTest/duplicateInterviewHrManager.json @@ -0,0 +1,17 @@ +{ + "interviews" : [ { + "position" : "Accountant", + "candidateIDs" : [ "-550871537", "-2024498055" ], + "date" : "15/10/2021", + "startTime" : "1400", + "duration" : "120", + "status" : "PENDING" + }, { + "position" : "Accountant", + "candidateIDs" : [ "-550871537", "-2024498055" ], + "date" : "15/10/2021", + "startTime" : "1400", + "duration" : "120", + "status" : "PENDING" + } ] +} diff --git a/src/test/data/JsonSerializableHrManagerInterviewsTest/invalidInterviewHrManager.json b/src/test/data/JsonSerializableHrManagerInterviewsTest/invalidInterviewHrManager.json new file mode 100644 index 00000000000..9c85a242bb5 --- /dev/null +++ b/src/test/data/JsonSerializableHrManagerInterviewsTest/invalidInterviewHrManager.json @@ -0,0 +1,10 @@ +{ + "interviews" : [ { + "position" : "Valid Position", + "candidateIDs" : [ "-550871537", "-2024498055", "-1834523193" ], + "date" : "2021/10/15", + "startTime" : "14:00", + "duration" : "PT1H20M", + "status" : "PENDING" + } ] +} diff --git a/src/test/data/JsonSerializableHrManagerInterviewsTest/typicalInterviewHrManager.json b/src/test/data/JsonSerializableHrManagerInterviewsTest/typicalInterviewHrManager.json new file mode 100644 index 00000000000..1a9a97a512f --- /dev/null +++ b/src/test/data/JsonSerializableHrManagerInterviewsTest/typicalInterviewHrManager.json @@ -0,0 +1,17 @@ +{ + "interviews" : [ { + "position" : "Accountant", + "candidateIDs" : [ ], + "date" : "15/10/2021", + "startTime" : "1400", + "duration" : "120", + "status" : "PENDING" + }, { + "position" : "Bookkeeper", + "candidateIDs" : [ ], + "date" : "15/12/2021", + "startTime" : "1400", + "duration" : "120", + "status" : "PENDING" + } ] +} diff --git a/src/test/data/JsonSerializableHrManagerPositionsTest/duplicatePositionHrManager.json b/src/test/data/JsonSerializableHrManagerPositionsTest/duplicatePositionHrManager.json new file mode 100644 index 00000000000..c359c805382 --- /dev/null +++ b/src/test/data/JsonSerializableHrManagerPositionsTest/duplicatePositionHrManager.json @@ -0,0 +1,9 @@ +{ + "positions" : [ { + "title" : "account manager", + "positionStatus" : "OPEN" + }, { + "title" : "account manager", + "positionStatus" : "OPEN" + } ] +} diff --git a/src/test/data/JsonSerializableHrManagerPositionsTest/invalidPositionHrManager.json b/src/test/data/JsonSerializableHrManagerPositionsTest/invalidPositionHrManager.json new file mode 100644 index 00000000000..31ce6fde032 --- /dev/null +++ b/src/test/data/JsonSerializableHrManagerPositionsTest/invalidPositionHrManager.json @@ -0,0 +1,6 @@ +{ + "positions" : [ { + "title" : "invalid position acc-manager", + "positionStatus" : "OPEN" + } ] +} diff --git a/src/test/data/JsonSerializableHrManagerPositionsTest/typicalPositionsHrManager.json b/src/test/data/JsonSerializableHrManagerPositionsTest/typicalPositionsHrManager.json new file mode 100644 index 00000000000..1a3742b718f --- /dev/null +++ b/src/test/data/JsonSerializableHrManagerPositionsTest/typicalPositionsHrManager.json @@ -0,0 +1,9 @@ +{ + "positions" : [ { + "title" : "Bookkeeper", + "positionStatus" : "OPEN" + }, { + "title" : "Administrative Assistant", + "positionStatus" : "OPEN" + } ] +} diff --git a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json index 1037548a9cd..efcdd42d02e 100644 --- a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json @@ -9,5 +9,5 @@ "z" : 99 } }, - "addressBookFilePath" : "addressbook.json" + "addressBookFilePath" : "candidates.json" } diff --git a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json index b819bed900a..0b9352d7eac 100644 --- a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json @@ -7,5 +7,5 @@ "y" : 100 } }, - "addressBookFilePath" : "addressbook.json" + "addressBookFilePath" : "candidates.json" } diff --git a/src/test/java/seedu/address/commons/core/ConfigTest.java b/src/test/java/seedu/address/commons/core/ConfigTest.java index 07cd7f73d53..4babaab5faa 100644 --- a/src/test/java/seedu/address/commons/core/ConfigTest.java +++ b/src/test/java/seedu/address/commons/core/ConfigTest.java @@ -1,9 +1,12 @@ package seedu.address.commons.core; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.logging.Level; + import org.junit.jupiter.api.Test; public class ConfigTest { @@ -19,9 +22,23 @@ public void toString_defaultObject_stringReturned() { @Test public void equalsMethod() { Config defaultConfig = new Config(); + Config otherConfig = new Config(); assertNotNull(defaultConfig); assertTrue(defaultConfig.equals(defaultConfig)); + + assertTrue(defaultConfig.hashCode() == otherConfig.hashCode()); + + assertFalse(defaultConfig.equals("String")); + + assertFalse(defaultConfig.equals(5)); } + @Test + public void getters_notNull() { + Config defaultConfig = new Config(); + assertNotNull(defaultConfig.getUserPrefsFilePath()); + + assertTrue(defaultConfig.getLogLevel() == Level.INFO); + } } diff --git a/src/test/java/seedu/address/commons/core/VersionTest.java b/src/test/java/seedu/address/commons/core/VersionTest.java index 495cd231554..e12a0a98141 100644 --- a/src/test/java/seedu/address/commons/core/VersionTest.java +++ b/src/test/java/seedu/address/commons/core/VersionTest.java @@ -1,6 +1,7 @@ package seedu.address.commons.core; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.testutil.Assert.assertThrows; @@ -59,6 +60,10 @@ public void versionComparable_validVersion_compareToIsCorrect() { another = new Version(11, 12, 13, false); assertTrue(one.compareTo(another) == 0); + //Test equals with different Objects + assertFalse(one.equals(null) || another.equals(null)); + assertFalse(one.equals(5) || another.equals("String")); + // Tests different patch one = new Version(0, 0, 5, false); another = new Version(0, 0, 0, false); @@ -93,6 +98,7 @@ public void versionComparable_validVersion_compareToIsCorrect() { one = new Version(2, 15, 0, true); another = new Version(2, 15, 0, false); assertTrue(one.compareTo(another) < 0); + assertTrue(another.compareTo(one) > 0); // Tests early access lower version vs not early access higher version compare by version number first one = new Version(2, 15, 0, true); diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index ad923ac249a..9f5a6ef4adb 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -3,10 +3,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.ADDRESS_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.NAME_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.POSITION_HR_MANAGER; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.AMY; @@ -17,17 +18,18 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.candidate.AddCandidateCommand; +import seedu.address.logic.candidate.ListCandidateCommand; +import seedu.address.logic.candidate.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyHrManager; import seedu.address.model.UserPrefs; import seedu.address.model.person.Person; -import seedu.address.storage.JsonAddressBookStorage; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; +import seedu.address.storage.JsonHrManagerStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.StorageManager; import seedu.address.testutil.PersonBuilder; @@ -43,10 +45,13 @@ public class LogicManagerTest { @BeforeEach public void setUp() { - JsonAddressBookStorage addressBookStorage = - new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json")); + JsonHrManagerStorage hrManagerStorage = + new JsonHrManagerStorage(temporaryFolder.resolve("HrManagerCandidates.json"), + temporaryFolder.resolve("HrManagerPositions.json"), + temporaryFolder.resolve("HrManagerInterViews.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json")); - StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage); + StorageManager storage = new StorageManager(hrManagerStorage, userPrefsStorage); + model.addPosition(new Position(new Title("HR Manager"))); logic = new LogicManager(model, storage); } @@ -58,31 +63,34 @@ public void execute_invalidCommandFormat_throwsParseException() { @Test public void execute_commandExecutionError_throwsCommandException() { - String deleteCommand = "delete 9"; + String deleteCommand = "delete_c 9"; assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } @Test public void execute_validCommand_success() throws Exception { - String listCommand = ListCommand.COMMAND_WORD; - assertCommandSuccess(listCommand, ListCommand.MESSAGE_SUCCESS, model); + String listCommand = ListCandidateCommand.COMMAND_WORD; + assertCommandSuccess(listCommand, ListCandidateCommand.MESSAGE_SUCCESS, model); } @Test public void execute_storageThrowsIoException_throwsCommandException() { - // Setup LogicManager with JsonAddressBookIoExceptionThrowingStub - JsonAddressBookStorage addressBookStorage = - new JsonAddressBookIoExceptionThrowingStub(temporaryFolder.resolve("ioExceptionAddressBook.json")); + // Setup LogicManager with JsonHrManagerIoExceptionThrowingStub + JsonHrManagerStorage addressBookStorage = + new JsonHrManagerIoExceptionThrowingStub(temporaryFolder.resolve("ioExceptionHrManagerCandidates.json"), + temporaryFolder.resolve("ioExceptionHrManagerPositions.json"), + temporaryFolder.resolve("ioExceptionHrManagerInterviews.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("ioExceptionUserPrefs.json")); StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage); logic = new LogicManager(model, storage); // Execute add command - String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY - + ADDRESS_DESC_AMY; - Person expectedPerson = new PersonBuilder(AMY).withTags().build(); + String addCommand = AddCandidateCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + POSITION_HR_MANAGER; + Person expectedPerson = new PersonBuilder(AMY).withTags().withRemark("").build(); ModelManager expectedModel = new ModelManager(); + expectedModel.addPosition(new Position(new Title("HR Manager"))); expectedModel.addPerson(expectedPerson); String expectedMessage = LogicManager.FILE_OPS_ERROR_MESSAGE + DUMMY_IO_EXCEPTION; assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel); @@ -93,6 +101,16 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPersonList().remove(0)); } + @Test + public void getFilteredPositionList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPositionList().remove(0)); + } + + @Test + public void getFilteredInterviewList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredInterviewList().remove(0)); + } + /** * Executes the command and confirms that * - no exceptions are thrown <br> @@ -129,7 +147,7 @@ private void assertCommandException(String inputCommand, String expectedMessage) */ private void assertCommandFailure(String inputCommand, Class<? extends Throwable> expectedException, String expectedMessage) { - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getHrManager(), new UserPrefs()); assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel); } @@ -149,13 +167,15 @@ private void assertCommandFailure(String inputCommand, Class<? extends Throwable /** * A stub class to throw an {@code IOException} when the save method is called. */ - private static class JsonAddressBookIoExceptionThrowingStub extends JsonAddressBookStorage { - private JsonAddressBookIoExceptionThrowingStub(Path filePath) { - super(filePath); + private static class JsonHrManagerIoExceptionThrowingStub extends JsonHrManagerStorage { + private JsonHrManagerIoExceptionThrowingStub(Path candidatesFilePath, Path positionsFilePath, + Path interviewFilePath) { + super(candidatesFilePath, positionsFilePath, interviewFilePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveHrManager(ReadOnlyHrManager hrManager, Path candidatesFilePath, + Path positionsFilePath, Path interviewFilePath) throws IOException { throw DUMMY_IO_EXCEPTION; } } diff --git a/src/test/java/seedu/address/logic/candidate/AddCandidateCommandIntegrationTest.java b/src/test/java/seedu/address/logic/candidate/AddCandidateCommandIntegrationTest.java new file mode 100644 index 00000000000..63c09ac3120 --- /dev/null +++ b/src/test/java/seedu/address/logic/candidate/AddCandidateCommandIntegrationTest.java @@ -0,0 +1,46 @@ +package seedu.address.logic.candidate; + +import static seedu.address.logic.candidate.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.candidate.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; +import seedu.address.testutil.PersonBuilder; + +/** + * Contains integration tests (interaction with the Model) for {@code AddCommand}. + */ +public class AddCandidateCommandIntegrationTest { + + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + } + + @Test + public void execute_newPerson_success() { + Person validPerson = new PersonBuilder().build(); + + Model expectedModel = new ModelManager(model.getHrManager(), new UserPrefs()); + expectedModel.addPerson(validPerson); + + assertCommandSuccess(new AddCandidateCommand(validPerson), model, + String.format(AddCandidateCommand.MESSAGE_SUCCESS, validPerson), expectedModel); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() { + Person personInList = model.getHrManager().getPersonList().get(0); + assertCommandFailure(new AddCandidateCommand(personInList), model, + String.format(AddCandidateCommand.MESSAGE_DUPLICATE_PERSON, personInList.getEmail())); + } + +} diff --git a/src/test/java/seedu/address/logic/candidate/AddCandidateCommandTest.java b/src/test/java/seedu/address/logic/candidate/AddCandidateCommandTest.java new file mode 100644 index 00000000000..43630c91af9 --- /dev/null +++ b/src/test/java/seedu/address/logic/candidate/AddCandidateCommandTest.java @@ -0,0 +1,209 @@ +package seedu.address.logic.candidate; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.model.position.Position.MESSAGE_POSITION_CLOSED; +import static seedu.address.model.position.Position.MESSAGE_POSITION_DOES_NOT_EXIST; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.BOB; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.HrManager; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; +import seedu.address.testutil.ModelStub; +import seedu.address.testutil.PersonBuilder; + +public class AddCandidateCommandTest { + + @Test + public void constructor_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new AddCandidateCommand(null)); + } + + @Test + public void execute_personAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); + Person validPerson = new PersonBuilder().build(); + + CommandResult commandResult = new AddCandidateCommand(validPerson).execute(modelStub); + + assertEquals(String.format(AddCandidateCommand.MESSAGE_SUCCESS, validPerson), + commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(validPerson), modelStub.personsAdded); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() { + Person validPerson = new PersonBuilder().build(); + AddCandidateCommand addCandidateCommand = new AddCandidateCommand(validPerson); + ModelStub modelStub = new ModelStubWithPerson(validPerson); + + assertThrows(CommandException.class, + String.format(AddCandidateCommand.MESSAGE_DUPLICATE_PERSON, validPerson.getEmail()), () -> + addCandidateCommand.execute(modelStub)); + } + + @Test + public void execute_positionDoesNotExist_throwsCommandException() { + Person validPerson = new PersonBuilder(ALICE).withPositions("Admin").build(); + ModelStubWithSomePositionsAndBob modelStub = new ModelStubWithSomePositionsAndBob(); + AddCandidateCommand addCandidateCommand = new AddCandidateCommand(validPerson); + String expectedMessage = String.format(MESSAGE_POSITION_DOES_NOT_EXIST, "Admin"); + + assertThrows(CommandException.class, expectedMessage, () -> + addCandidateCommand.execute(modelStub)); + } + + @Test + public void execute_positionIsClosed_throwsCommandException() { + Person validPerson = new PersonBuilder(ALICE).withPositions(ADMIN_ASSISTANT.getTitle().fullTitle).build(); + ModelStubWithSomePositionsAndBob modelStub = new ModelStubWithSomePositionsAndBob(); + AddCandidateCommand addCandidateCommand = new AddCandidateCommand(validPerson); + + String expectedMessage = String.format(MESSAGE_POSITION_CLOSED, ADMIN_ASSISTANT.getTitle().fullTitle); + + assertThrows(CommandException.class, expectedMessage, () -> + addCandidateCommand.execute(modelStub)); + } + + @Test + public void equals() { + Person alice = new PersonBuilder().withName("Alice").build(); + Person bob = new PersonBuilder().withName("Bob").build(); + AddCandidateCommand addAliceCommand = new AddCandidateCommand(alice); + AddCandidateCommand addBobCommand = new AddCandidateCommand(bob); + + // same object -> returns true + assertTrue(addAliceCommand.equals(addAliceCommand)); + + // same values -> returns true + AddCandidateCommand addAliceCommandCopy = new AddCandidateCommand(alice); + assertTrue(addAliceCommand.equals(addAliceCommandCopy)); + + // different types -> returns false + assertFalse(addAliceCommand.equals(1)); + + // null -> returns false + assertFalse(addAliceCommand.equals(null)); + + // different person -> returns false + assertFalse(addAliceCommand.equals(addBobCommand)); + } + + + /** + * A Model stub that contains a single person. + */ + private class ModelStubWithPerson extends ModelStub { + private final Person person; + + ModelStubWithPerson(Person person) { + requireNonNull(person); + this.person = person; + } + + @Override + public boolean hasPerson(Person person) { + requireNonNull(person); + return this.person.isSamePerson(person); + } + } + + /** + * A Model stub that has position ADMIN_ASSISTANT and a person BOB. + */ + private class ModelStubWithSomePositionsAndBob extends ModelStub { + private final Set<Position> positions = new HashSet<>(); + private final Set<Person> persons = new HashSet<>(); + + ModelStubWithSomePositionsAndBob() { + persons.add(BOB); + positions.add(ADMIN_ASSISTANT); + } + + @Override + public boolean hasPerson(Person person) { + requireNonNull(person); + return this.persons.contains(person); + } + + @Override + public boolean hasPosition(Position position) { + for (Position p : positions) { + if (p.isSamePosition(position)) { + return true; + } + } + return false; + } + + @Override + public Position getPositionReference(Position position) { + return position; + } + + //For some reason p.getStatus() is always open even when building with closed + @Override + public boolean isPositionClosed(Position p) { + return true; + } + + @Override + public void addPerson(Person person) { + persons.add(person); + } + } + + /** + * A Model stub that always accept the person being added. + */ + private class ModelStubAcceptingPersonAdded extends ModelStub { + final ArrayList<Person> personsAdded = new ArrayList<>(); + + @Override + public boolean hasPerson(Person person) { + requireNonNull(person); + return personsAdded.stream().anyMatch(person::isSamePerson); + } + + @Override + public void addPerson(Person person) { + requireNonNull(person); + personsAdded.add(person); + } + + @Override + public ReadOnlyHrManager getHrManager() { + return new HrManager(); + } + + @Override + public boolean hasPosition(Position position) { + return true; + } + + @Override + public boolean isPositionClosed(Position p) { + return false; + } + + @Override + public Position getPositionReference(Position position) { + return position; + } + } +} diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/candidate/CommandResultTest.java similarity index 63% rename from src/test/java/seedu/address/logic/commands/CommandResultTest.java rename to src/test/java/seedu/address/logic/candidate/CommandResultTest.java index 4f3eb46e9ef..75bf298f3fa 100644 --- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java +++ b/src/test/java/seedu/address/logic/candidate/CommandResultTest.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.address.logic.candidate; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -7,14 +7,15 @@ import org.junit.jupiter.api.Test; +import seedu.address.logic.CommandResult; + public class CommandResultTest { @Test public void equals() { - CommandResult commandResult = new CommandResult("feedback"); + CommandResult commandResult = new CommandResult("feedback", CommandResult.CommandType.GENERAL); // same values -> returns true - assertTrue(commandResult.equals(new CommandResult("feedback"))); - assertTrue(commandResult.equals(new CommandResult("feedback", false, false))); + assertTrue(commandResult.equals(new CommandResult("feedback", CommandResult.CommandType.GENERAL))); // same object -> returns true assertTrue(commandResult.equals(commandResult)); @@ -26,29 +27,34 @@ public void equals() { assertFalse(commandResult.equals(0.5f)); // different feedbackToUser value -> returns false - assertFalse(commandResult.equals(new CommandResult("different"))); + assertFalse(commandResult.equals(new CommandResult("different", + CommandResult.CommandType.GENERAL))); - // different showHelp value -> returns false - assertFalse(commandResult.equals(new CommandResult("feedback", true, false))); + // CommandType.HELP -> returns false + assertFalse(commandResult.equals(new CommandResult("feedback", CommandResult.CommandType.HELP))); - // different exit value -> returns false - assertFalse(commandResult.equals(new CommandResult("feedback", false, true))); + // CommandType.EXIT -> returns false + assertFalse(commandResult.equals(new CommandResult("feedback", CommandResult.CommandType.EXIT))); } @Test public void hashcode() { - CommandResult commandResult = new CommandResult("feedback"); + CommandResult commandResult = new CommandResult("feedback", CommandResult.CommandType.GENERAL); // same values -> returns same hashcode - assertEquals(commandResult.hashCode(), new CommandResult("feedback").hashCode()); + assertEquals(commandResult.hashCode(), new CommandResult("feedback", + CommandResult.CommandType.GENERAL).hashCode()); // different feedbackToUser value -> returns different hashcode - assertNotEquals(commandResult.hashCode(), new CommandResult("different").hashCode()); + assertNotEquals(commandResult.hashCode(), new CommandResult("different", + CommandResult.CommandType.GENERAL).hashCode()); - // different showHelp value -> returns different hashcode - assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false).hashCode()); + // CommandType.HELP -> returns different hashcode + assertNotEquals(commandResult.hashCode(), new CommandResult( + "feedback", CommandResult.CommandType.HELP).hashCode()); - // different exit value -> returns different hashcode - assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true).hashCode()); + // CommandType.EXIT -> returns different hashcode + assertNotEquals(commandResult.hashCode(), new CommandResult( + "feedback", CommandResult.CommandType.EXIT).hashCode()); } } diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/candidate/CommandTestUtil.java similarity index 60% rename from src/test/java/seedu/address/logic/commands/CommandTestUtil.java rename to src/test/java/seedu/address/logic/candidate/CommandTestUtil.java index 643a1d08069..47bba5a4ea6 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/candidate/CommandTestUtil.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.address.logic.candidate; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -6,6 +6,7 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.testutil.Assert.assertThrows; @@ -14,10 +15,12 @@ import java.util.List; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.HrManager; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.FindCandidateCommandPredicate; import seedu.address.model.person.Person; import seedu.address.testutil.EditPersonDescriptorBuilder; @@ -32,10 +35,16 @@ public class CommandTestUtil { public static final String VALID_PHONE_BOB = "22222222"; public static final String VALID_EMAIL_AMY = "amy@example.com"; public static final String VALID_EMAIL_BOB = "bob@example.com"; + public static final String VALID_REMARK_AMY = "AMYMY"; + public static final String VALID_REMARK_BOB = "BOBBY"; public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1"; public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3"; public static final String VALID_TAG_HUSBAND = "husband"; public static final String VALID_TAG_FRIEND = "friend"; + public static final String VALID_STATUS_APPLIED = "Applied"; + public static final String VALID_TITLE_BOOKKEEPER = "Bookkeeper"; + public static final String VALID_TITLE_HR_MANAGER = "HR Manager"; + public static final String VALID_TITLE_ADMIN_ASSISTANT = "Administrative Assistant"; public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; @@ -47,18 +56,21 @@ public class CommandTestUtil { public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB; public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND; public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND; + public static final String POSITION_HR_MANAGER = " " + PREFIX_POSITION + VALID_TITLE_HR_MANAGER; + public static final String POSITION_ADMIN_ASSISTANT = " " + PREFIX_POSITION + VALID_TITLE_ADMIN_ASSISTANT; public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags + public static final String INVALID_POSITION_DESC = " " + PREFIX_POSITION + "!accountant"; public static final String PREAMBLE_WHITESPACE = "\t \r \n"; public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; - public static final EditCommand.EditPersonDescriptor DESC_AMY; - public static final EditCommand.EditPersonDescriptor DESC_BOB; + public static final EditCandidateCommand.EditPersonDescriptor DESC_AMY; + public static final EditCandidateCommand.EditPersonDescriptor DESC_BOB; static { DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) @@ -75,7 +87,7 @@ public class CommandTestUtil { * - the {@code actualModel} matches {@code expectedModel} */ public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, - Model expectedModel) { + Model expectedModel) { try { CommandResult result = command.execute(actualModel); assertEquals(expectedCommandResult, result); @@ -90,11 +102,58 @@ public static void assertCommandSuccess(Command command, Model actualModel, Comm * that takes a string {@code expectedMessage}. */ public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, - Model expectedModel) { - CommandResult expectedCommandResult = new CommandResult(expectedMessage); + Model expectedModel) { + CommandResult expectedCommandResult = new CommandResult(expectedMessage, CommandResult.CommandType.CANDIDATE); assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); } + /** + * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} + * that takes a string {@code expectedMessage}. + */ + public static void assertListCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + CommandResult expectedCommandResult = new CommandResult(expectedMessage, CommandResult.CommandType.LIST_C); + assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); + } + + private static boolean isEditCCommand(Command command) { + String commandClassName = command.getClass().getSimpleName(); + return commandClassName.equals("EditCandidateCommand"); + } + + /** + * Executes the given {@code command}, confirms that <br> + * - the given {@code command} is an edit command <br> + * - the returned {@link CommandResult} matches {@code expectedCommandResult} <br> + * - the {@code actualModel} matches {@code expectedModel} + */ + public static void assertEditCandidateCommandSuccess(Command command, Model actualModel, + CommandResult expectedEditCommandResult, Model expectedModel) { + try { + CommandResult result = command.execute(actualModel); + assertEquals(expectedEditCommandResult, result); + assertEquals(expectedModel, actualModel); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Convenience wrapper to {@link #assertEditCandidateCommandSuccess(Command, Model, CommandResult, Model)} + * that takes a string {@code expectedMessage}. + */ + public static void assertEditCandidateCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + if (!isEditCCommand(command)) { + throw new AssertionError("Command should be an EditCandidateCommand"); + } + + CommandResult expectedCommandResult = new CommandResult(expectedMessage, + CommandResult.CommandType.CANDIDATE); + assertEditCandidateCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); + } + /** * Executes the given {@code command}, confirms that <br> * - a {@code CommandException} is thrown <br> @@ -104,13 +163,14 @@ public static void assertCommandSuccess(Command command, Model actualModel, Stri public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) { // we are unable to defensively copy the model for comparison later, so we can // only do so by copying its components. - AddressBook expectedAddressBook = new AddressBook(actualModel.getAddressBook()); + HrManager expectedAddressBook = new HrManager(actualModel.getHrManager()); List<Person> expectedFilteredList = new ArrayList<>(actualModel.getFilteredPersonList()); assertThrows(CommandException.class, expectedMessage, () -> command.execute(actualModel)); - assertEquals(expectedAddressBook, actualModel.getAddressBook()); + assertEquals(expectedAddressBook, actualModel.getHrManager()); assertEquals(expectedFilteredList, actualModel.getFilteredPersonList()); } + /** * Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in the * {@code model}'s address book. @@ -120,7 +180,7 @@ public static void showPersonAtIndex(Model model, Index targetIndex) { Person person = model.getFilteredPersonList().get(targetIndex.getZeroBased()); final String[] splitName = person.getName().fullName.split("\\s+"); - model.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0]))); + model.updateFilteredPersonList(new FindCandidateCommandPredicate(Arrays.asList(splitName[0]))); assertEquals(1, model.getFilteredPersonList().size()); } diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/candidate/DeleteCandidateCommandTest.java similarity index 54% rename from src/test/java/seedu/address/logic/commands/DeleteCommandTest.java rename to src/test/java/seedu/address/logic/candidate/DeleteCandidateCommandTest.java index 45a8c910ba1..41db5702445 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/address/logic/candidate/DeleteCandidateCommandTest.java @@ -1,13 +1,13 @@ -package seedu.address.logic.commands; +package seedu.address.logic.candidate; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.logic.candidate.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.candidate.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.candidate.CommandTestUtil.showPersonAtIndex; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; import org.junit.jupiter.api.Test; @@ -22,29 +22,29 @@ * Contains integration tests (interaction with the Model) and unit tests for * {@code DeleteCommand}. */ -public class DeleteCommandTest { +public class DeleteCandidateCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); @Test public void execute_validIndexUnfilteredList_success() { Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); + DeleteCandidateCommand deleteCandidateCommand = new DeleteCandidateCommand(INDEX_FIRST_PERSON); - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); + String expectedMessage = String.format(DeleteCandidateCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); - ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + ModelManager expectedModel = new ModelManager(model.getHrManager(), new UserPrefs()); expectedModel.deletePerson(personToDelete); - assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); + assertCommandSuccess(deleteCandidateCommand, model, expectedMessage, expectedModel); } @Test public void execute_invalidIndexUnfilteredList_throwsCommandException() { Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); - DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); + DeleteCandidateCommand deleteCandidateCommand = new DeleteCandidateCommand(outOfBoundIndex); - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandFailure(deleteCandidateCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } @Test @@ -52,15 +52,15 @@ public void execute_validIndexFilteredList_success() { showPersonAtIndex(model, INDEX_FIRST_PERSON); Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); + DeleteCandidateCommand deleteCandidateCommand = new DeleteCandidateCommand(INDEX_FIRST_PERSON); - String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); + String expectedMessage = String.format(DeleteCandidateCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getHrManager(), new UserPrefs()); expectedModel.deletePerson(personToDelete); showNoPerson(expectedModel); - assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); + assertCommandSuccess(deleteCandidateCommand, model, expectedMessage, expectedModel); } @Test @@ -69,23 +69,23 @@ public void execute_invalidIndexFilteredList_throwsCommandException() { Index outOfBoundIndex = INDEX_SECOND_PERSON; // ensures that outOfBoundIndex is still in bounds of address book list - assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); + assertTrue(outOfBoundIndex.getZeroBased() < model.getHrManager().getPersonList().size()); - DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); + DeleteCandidateCommand deleteCandidateCommand = new DeleteCandidateCommand(outOfBoundIndex); - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandFailure(deleteCandidateCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } @Test public void equals() { - DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON); - DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON); + DeleteCandidateCommand deleteFirstCommand = new DeleteCandidateCommand(INDEX_FIRST_PERSON); + DeleteCandidateCommand deleteSecondCommand = new DeleteCandidateCommand(INDEX_SECOND_PERSON); // same object -> returns true assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); // same values -> returns true - DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON); + DeleteCandidateCommand deleteFirstCommandCopy = new DeleteCandidateCommand(INDEX_FIRST_PERSON); assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); // different types -> returns false diff --git a/src/test/java/seedu/address/logic/candidate/EditCandidateCommandTest.java b/src/test/java/seedu/address/logic/candidate/EditCandidateCommandTest.java new file mode 100644 index 00000000000..37b7510cd67 --- /dev/null +++ b/src/test/java/seedu/address/logic/candidate/EditCandidateCommandTest.java @@ -0,0 +1,281 @@ +package seedu.address.logic.candidate; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.candidate.CommandTestUtil.DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.DESC_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.candidate.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.candidate.CommandTestUtil.assertEditCandidateCommandSuccess; +import static seedu.address.logic.candidate.CommandTestUtil.showPersonAtIndex; +import static seedu.address.model.position.Position.MESSAGE_POSITION_CLOSED; +import static seedu.address.model.position.Position.MESSAGE_POSITION_DOES_NOT_EXIST; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.BOB; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; +import static seedu.address.testutil.TypicalPositions.CLOSED_POSITION_CLERK; +import static seedu.address.testutil.TypicalPositions.HR_MANAGER; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.candidate.EditCandidateCommand.EditPersonDescriptor; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.logic.general.ClearCommand; +import seedu.address.model.HrManager; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; +import seedu.address.testutil.EditPersonDescriptorBuilder; +import seedu.address.testutil.ModelStub; +import seedu.address.testutil.PersonBuilder; + +/** + * Contains integration tests (interaction with the Model) and unit tests for EditCommand. + */ +public class EditCandidateCommandTest { + + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void execute_allFieldsSpecifiedUnfilteredList_success() { + Person editedPerson = new PersonBuilder().withRemark("She likes aardvarks.").build(); + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); + EditCandidateCommand editCandidateCommand = new EditCandidateCommand(INDEX_FIRST_PERSON, descriptor); + + String expectedMessage = String.format(EditCandidateCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); + + assertEditCandidateCommandSuccess(editCandidateCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_someFieldsSpecifiedUnfilteredList_success() { + Index indexLastPerson = Index.fromOneBased(model.getFilteredPersonList().size()); + Person lastPerson = model.getFilteredPersonList().get(indexLastPerson.getZeroBased()); + + PersonBuilder personInList = new PersonBuilder(lastPerson); + Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withTags(VALID_TAG_HUSBAND).build(); + + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) + .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build(); + EditCandidateCommand editCandidateCommand = new EditCandidateCommand(indexLastPerson, descriptor); + + String expectedMessage = String.format(EditCandidateCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + expectedModel.setPerson(lastPerson, editedPerson); + + CommandTestUtil.assertEditCandidateCommandSuccess(editCandidateCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_noFieldSpecifiedUnfilteredList_success() { + EditCandidateCommand editCandidateCommand = new EditCandidateCommand(INDEX_FIRST_PERSON, + new EditPersonDescriptor()); + Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + + String expectedMessage = String.format(EditCandidateCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + + CommandTestUtil.assertEditCandidateCommandSuccess(editCandidateCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_filteredList_success() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person editedPerson = new PersonBuilder(personInFilteredList).withPhone(VALID_PHONE_BOB).build(); + EditCandidateCommand editCandidateCommand = new EditCandidateCommand(INDEX_FIRST_PERSON, + new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).build()); + + String expectedMessage = String.format(EditCandidateCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + expectedModel.setPerson(firstPerson, editedPerson); + showPersonAtIndex(expectedModel, INDEX_FIRST_PERSON); + + assertEditCandidateCommandSuccess(editCandidateCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_duplicatePersonUnfilteredList_failure() { + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).build(); + EditCandidateCommand editCandidateCommand = new EditCandidateCommand(INDEX_SECOND_PERSON, descriptor); + + assertCommandFailure(editCandidateCommand, model, + String.format(EditCandidateCommand.MESSAGE_DUPLICATE_PERSON, firstPerson.getEmail())); + } + + @Test + public void execute_duplicatePersonFilteredList_failure() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + // edit person in filtered list into a duplicate in address book + Person personInList = model.getHrManager().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); + EditCandidateCommand editCandidateCommand = new EditCandidateCommand(INDEX_FIRST_PERSON, + new EditPersonDescriptorBuilder(personInList).build()); + + assertCommandFailure(editCandidateCommand, model, + String.format(EditCandidateCommand.MESSAGE_DUPLICATE_PERSON, personInList.getEmail())); + } + + @Test + public void execute_invalidPersonIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build(); + EditCandidateCommand editCandidateCommand = new EditCandidateCommand(outOfBoundIndex, descriptor); + + assertCommandFailure(editCandidateCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + /** + * Edit filtered list where index is larger than size of filtered list, + * but smaller than size of address book + */ + @Test + public void execute_invalidPersonIndexFilteredList_failure() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + Index outOfBoundIndex = INDEX_SECOND_PERSON; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getHrManager().getPersonList().size()); + + EditCandidateCommand editCandidateCommand = new EditCandidateCommand(outOfBoundIndex, + new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); + + assertCommandFailure(editCandidateCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void equals() { + final EditCandidateCommand standardCommand = new EditCandidateCommand(INDEX_FIRST_PERSON, DESC_AMY); + + // same values -> returns true + EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY); + EditCandidateCommand commandWithSameValues = new EditCandidateCommand(INDEX_FIRST_PERSON, copyDescriptor); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(standardCommand.equals(new EditCandidateCommand(INDEX_SECOND_PERSON, DESC_AMY))); + + // different descriptor -> returns false + assertFalse(standardCommand.equals(new EditCandidateCommand(INDEX_FIRST_PERSON, DESC_BOB))); + } + + @Test + public void execute_positionDoesNotExist_throwsCommandException() { + Set<Position> tempSet = new HashSet<>(); + tempSet.add(new Position(new Title("Admin"))); + EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + editPersonDescriptor.setPositions(tempSet); + EditCandidateCommand editCandidateCommand = new EditCandidateCommand(INDEX_FIRST_PERSON, + editPersonDescriptor); + ModelStubWithObservable modelStub = new ModelStubWithObservable(); + String expectedMessage = String.format(MESSAGE_POSITION_DOES_NOT_EXIST, "Admin"); + + assertThrows(CommandException.class, expectedMessage, () -> + editCandidateCommand.execute(modelStub)); + } + + @Test + public void execute_positionIsClosed_throwsCommandException() { + Set<Position> tempSet = new HashSet<>(); + tempSet.add(CLOSED_POSITION_CLERK); + EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + editPersonDescriptor.setPositions(tempSet); + EditCandidateCommand editCandidateCommand = new EditCandidateCommand(INDEX_FIRST_PERSON, + editPersonDescriptor); + ModelStubWithObservable modelStub = new ModelStubWithObservable(); + + String expectedMessage = String.format(MESSAGE_POSITION_CLOSED, CLOSED_POSITION_CLERK.getTitle().fullTitle); + + assertThrows(CommandException.class, expectedMessage, () -> + editCandidateCommand.execute(modelStub)); + } + + /** + * A Model stub that has position ADMIN_ASSISTANT and a person BOB. + */ + private class ModelStubWithObservable extends ModelStub { + private ObservableList<Person> persons = FXCollections.observableArrayList(); + private ObservableList<Position> positions = FXCollections.observableArrayList(); + + ModelStubWithObservable() { + persons.add(BOB); + persons.add(ALICE); + positions.add(ADMIN_ASSISTANT); + positions.add(HR_MANAGER); + positions.add(CLOSED_POSITION_CLERK); + } + + @Override + public boolean hasPerson(Person person) { + requireNonNull(person); + return this.persons.contains(person); + } + + @Override + public boolean hasPosition(Position position) { + for (Position p : positions) { + if (p.isSamePosition(position)) { + return true; + } + } + return false; + } + + //For some reason p.getStatus() is always open even when building with closed + @Override + public boolean isPositionClosed(Position p) { + return true; + } + + @Override + public void addPerson(Person person) { + persons.add(person); + } + + @Override + public Position getPositionReference(Position position) { + return position; + } + + @Override + public ObservableList<Person> getFilteredPersonList() { + return persons; + } + } + +} diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/candidate/EditPersonDescriptorTest.java similarity index 65% rename from src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java rename to src/test/java/seedu/address/logic/candidate/EditPersonDescriptorTest.java index e0288792e72..ca9e6723922 100644 --- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java +++ b/src/test/java/seedu/address/logic/candidate/EditPersonDescriptorTest.java @@ -1,18 +1,19 @@ -package seedu.address.logic.commands; +package seedu.address.logic.candidate; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.candidate.CommandTestUtil.DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.DESC_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TITLE_ADMIN_ASSISTANT; import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.candidate.EditCandidateCommand.EditPersonDescriptor; import seedu.address.testutil.EditPersonDescriptorBuilder; public class EditPersonDescriptorTest { @@ -54,5 +55,9 @@ public void equals() { // different tags -> returns false editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_HUSBAND).build(); assertFalse(DESC_AMY.equals(editedAmy)); + + // different positions -> return false + editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withPositions(VALID_TITLE_ADMIN_ASSISTANT).build(); + assertFalse(DESC_AMY.equals(editedAmy)); } } diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/address/logic/candidate/ExitCommandTest.java similarity index 55% rename from src/test/java/seedu/address/logic/commands/ExitCommandTest.java rename to src/test/java/seedu/address/logic/candidate/ExitCommandTest.java index 9533c473875..72619e597ee 100644 --- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java +++ b/src/test/java/seedu/address/logic/candidate/ExitCommandTest.java @@ -1,10 +1,12 @@ -package seedu.address.logic.commands; +package seedu.address.logic.candidate; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT; +import static seedu.address.logic.candidate.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.general.ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT; import org.junit.jupiter.api.Test; +import seedu.address.logic.CommandResult; +import seedu.address.logic.general.ExitCommand; import seedu.address.model.Model; import seedu.address.model.ModelManager; @@ -14,7 +16,8 @@ public class ExitCommandTest { @Test public void execute_exit_success() { - CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, + CommandResult.CommandType.EXIT); assertCommandSuccess(new ExitCommand(), model, expectedCommandResult, expectedModel); } } diff --git a/src/test/java/seedu/address/logic/candidate/FindCandidateCommandTest.java b/src/test/java/seedu/address/logic/candidate/FindCandidateCommandTest.java new file mode 100644 index 00000000000..4c4b6be6cd4 --- /dev/null +++ b/src/test/java/seedu/address/logic/candidate/FindCandidateCommandTest.java @@ -0,0 +1,116 @@ +package seedu.address.logic.candidate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; +import static seedu.address.testutil.TypicalPersons.CARL; +import static seedu.address.testutil.TypicalPersons.ELLE; +import static seedu.address.testutil.TypicalPersons.FIONA; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.FindCandidateCommandPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code FindCommand}. + */ +public class FindCandidateCommandTest { + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void equals() { + FindCandidateCommandPredicate firstPredicate = + new FindCandidateCommandPredicate(Collections.singletonList("first")); + FindCandidateCommandPredicate secondPredicate = + new FindCandidateCommandPredicate(Collections.singletonList("second")); + + FindCandidateCommand findFirstCommand = new FindCandidateCommand(firstPredicate); + FindCandidateCommand findSecondCommand = new FindCandidateCommand(secondPredicate); + + // same object -> returns true + assertTrue(findFirstCommand.equals(findFirstCommand)); + + // same values -> returns true + FindCandidateCommand findFirstCommandCopy = new FindCandidateCommand(firstPredicate); + assertTrue(findFirstCommand.equals(findFirstCommandCopy)); + + // different types -> returns false + assertFalse(findFirstCommand.equals(1)); + + // null -> returns false + assertFalse(findFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(findFirstCommand.equals(findSecondCommand)); + } + + @Test + public void execute_multipleKeywords_multiplePersonsFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); + FindCandidateCommandPredicate predicate = preparePredicate("Kurz Elle Kunz"); + Command command = new FindCandidateCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + assertEquals(Arrays.asList(CARL, ELLE, FIONA), expectedModel.getFilteredPersonList()); + + //have to create a CommandResult manually because assertSuccess uses single parameter constructor + CommandResult expectedCommandResult = new CommandResult(expectedMessage, + CommandResult.CommandType.FIND_C); + + assertCommandSuccess(command, model, expectedCommandResult, expectedModel); + } + + + @Test + public void execute_personDoesNotExist_noPersonFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); + FindCandidateCommandPredicate predicate = preparePredicate("John"); + Command command = new FindCandidateCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + assertEquals(Arrays.asList(), expectedModel.getFilteredPersonList()); + + //have to create a CommandResult manually because assertSuccess uses single parameter constructor + CommandResult expectedCommandResult = new CommandResult(expectedMessage, + CommandResult.CommandType.FIND_C); + + assertCommandSuccess(command, model, expectedCommandResult, expectedModel); + } + + + /** + * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. + */ + private FindCandidateCommandPredicate preparePredicate(String userInput) { + List<String> output = new ArrayList<String>(Arrays.asList(userInput.split("\\s+"))); + output.removeAll(Arrays.asList("", null)); + return new FindCandidateCommandPredicate(output); + } + + + /** + * Overrides the method in CommandTestUtil for CommandResult created by find_c + */ + private void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, + Model expectedModel) { + try { + CommandResult result = command.execute(actualModel); + assertEquals(expectedCommandResult, result); + assertEquals(expectedModel, actualModel); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } +} diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/candidate/HelpCommandTest.java similarity index 56% rename from src/test/java/seedu/address/logic/commands/HelpCommandTest.java rename to src/test/java/seedu/address/logic/candidate/HelpCommandTest.java index 4904fc4352e..190de8c0972 100644 --- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java +++ b/src/test/java/seedu/address/logic/candidate/HelpCommandTest.java @@ -1,10 +1,12 @@ -package seedu.address.logic.commands; +package seedu.address.logic.candidate; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.HelpCommand.SHOWING_HELP_MESSAGE; +import static seedu.address.logic.candidate.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.general.HelpCommand.SHOWING_HELP_MESSAGE; import org.junit.jupiter.api.Test; +import seedu.address.logic.CommandResult; +import seedu.address.logic.general.HelpCommand; import seedu.address.model.Model; import seedu.address.model.ModelManager; @@ -14,7 +16,8 @@ public class HelpCommandTest { @Test public void execute_help_success() { - CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false); + CommandResult expectedCommandResult = new CommandResult( + SHOWING_HELP_MESSAGE, CommandResult.CommandType.HELP); assertCommandSuccess(new HelpCommand(), model, expectedCommandResult, expectedModel); } } diff --git a/src/test/java/seedu/address/logic/candidate/ListCandidateCommandTest.java b/src/test/java/seedu/address/logic/candidate/ListCandidateCommandTest.java new file mode 100644 index 00000000000..bbedd7d9af8 --- /dev/null +++ b/src/test/java/seedu/address/logic/candidate/ListCandidateCommandTest.java @@ -0,0 +1,41 @@ +package seedu.address.logic.candidate; + +import static seedu.address.logic.candidate.CommandTestUtil.assertListCommandSuccess; +import static seedu.address.logic.candidate.CommandTestUtil.showPersonAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +/** + * Contains integration tests (interaction with the Model) and unit tests for ListCommand. + */ +public class ListCandidateCommandTest { + + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + expectedModel = new ModelManager(model.getHrManager(), new UserPrefs()); + } + + @Test + public void execute_listIsNotFiltered_showsSameList() { + assertListCommandSuccess(new ListCandidateCommand(), model, ListCandidateCommand.MESSAGE_SUCCESS, + expectedModel); + } + + @Test + public void execute_listIsFiltered_showsEverything() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + assertListCommandSuccess(new ListCandidateCommand(), model, ListCandidateCommand.MESSAGE_SUCCESS, + expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/candidate/RemarkCandidateCommandTest.java b/src/test/java/seedu/address/logic/candidate/RemarkCandidateCommandTest.java new file mode 100644 index 00000000000..0182a5c9415 --- /dev/null +++ b/src/test/java/seedu/address/logic/candidate/RemarkCandidateCommandTest.java @@ -0,0 +1,135 @@ +package seedu.address.logic.candidate; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_REMARK_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_REMARK_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.candidate.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.candidate.CommandTestUtil.showPersonAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.general.ClearCommand; +import seedu.address.model.HrManager; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; +import seedu.address.model.person.Remark; +import seedu.address.testutil.PersonBuilder; + +/** + * Contains integration tests (interaction with the Model) and unit tests for RemarkCommand. + */ +public class RemarkCandidateCommandTest { + + private static final String REMARK_STUB = "Some remark"; + + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void execute_addRemarkUnfilteredList_success() { + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person editedPerson = new PersonBuilder(firstPerson).withRemark(REMARK_STUB).build(); + + RemarkCandidateCommand remarkCandidateCommand = new RemarkCandidateCommand(INDEX_FIRST_PERSON, + new Remark(editedPerson.getRemark().value)); + + String expectedMessage = String.format(RemarkCandidateCommand.MESSAGE_ADD_REMARK_SUCCESS, editedPerson); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + expectedModel.setPerson(firstPerson, editedPerson); + + assertCommandSuccess(remarkCandidateCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_deleteRemarkUnfilteredList_success() { + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person editedPerson = new PersonBuilder(firstPerson).withRemark("").build(); + + RemarkCandidateCommand remarkCandidateCommand = new RemarkCandidateCommand(INDEX_FIRST_PERSON, + new Remark(editedPerson.getRemark().toString())); + + String expectedMessage = String.format(RemarkCandidateCommand.MESSAGE_DELETE_REMARK_SUCCESS, editedPerson); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + expectedModel.setPerson(firstPerson, editedPerson); + + assertCommandSuccess(remarkCandidateCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_filteredList_success() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person editedPerson = new PersonBuilder(model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased())) + .withRemark(REMARK_STUB).build(); + + RemarkCandidateCommand remarkCandidateCommand = new RemarkCandidateCommand(INDEX_FIRST_PERSON, + new Remark(editedPerson.getRemark().value)); + + String expectedMessage = String.format(RemarkCandidateCommand.MESSAGE_ADD_REMARK_SUCCESS, editedPerson); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + expectedModel.setPerson(firstPerson, editedPerson); + showPersonAtIndex(expectedModel, INDEX_FIRST_PERSON); + + assertCommandSuccess(remarkCandidateCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidPersonIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + RemarkCandidateCommand remarkCandidateCommand = new RemarkCandidateCommand(outOfBoundIndex, + new Remark(VALID_REMARK_BOB)); + + assertCommandFailure(remarkCandidateCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + /** + * Edit filtered list where index is larger than size of filtered list, + * but smaller than size of address book + */ + @Test + public void execute_invalidPersonIndexFilteredList_failure() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + Index outOfBoundIndex = INDEX_SECOND_PERSON; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getHrManager().getPersonList().size()); + + RemarkCandidateCommand remarkCandidateCommand = new RemarkCandidateCommand(outOfBoundIndex, + new Remark(VALID_REMARK_BOB)); + + assertCommandFailure(remarkCandidateCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void equals() { + final RemarkCandidateCommand standardCommand = new RemarkCandidateCommand(INDEX_FIRST_PERSON, + new Remark(VALID_REMARK_AMY)); + // same values -> returns true + RemarkCandidateCommand commandWithSameValues = new RemarkCandidateCommand(INDEX_FIRST_PERSON, + new Remark(VALID_REMARK_AMY)); + assertTrue(standardCommand.equals(commandWithSameValues)); + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + // null -> returns false + assertFalse(standardCommand.equals(null)); + // different types -> returns false + assertFalse(standardCommand.equals(new ClearCommand())); + // different index -> returns false + assertFalse(standardCommand.equals(new RemarkCandidateCommand(INDEX_SECOND_PERSON, + new Remark(VALID_REMARK_AMY)))); + // different remark -> returns false + assertFalse(standardCommand.equals(new RemarkCandidateCommand(INDEX_FIRST_PERSON, + new Remark(VALID_REMARK_BOB)))); + } +} diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java deleted file mode 100644 index cb8714bb055..00000000000 --- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.logic.commands; - -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.PersonBuilder; - -/** - * Contains integration tests (interaction with the Model) for {@code AddCommand}. - */ -public class AddCommandIntegrationTest { - - private Model model; - - @BeforeEach - public void setUp() { - model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - } - - @Test - public void execute_newPerson_success() { - Person validPerson = new PersonBuilder().build(); - - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - expectedModel.addPerson(validPerson); - - assertCommandSuccess(new AddCommand(validPerson), model, - String.format(AddCommand.MESSAGE_SUCCESS, validPerson), expectedModel); - } - - @Test - public void execute_duplicatePerson_throwsCommandException() { - Person personInList = model.getAddressBook().getPersonList().get(0); - assertCommandFailure(new AddCommand(personInList), model, AddCommand.MESSAGE_DUPLICATE_PERSON); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java deleted file mode 100644 index 5865713d5dd..00000000000 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.PersonBuilder; - -public class AddCommandTest { - - @Test - public void constructor_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new AddCommand(null)); - } - - @Test - public void execute_personAcceptedByModel_addSuccessful() throws Exception { - ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); - Person validPerson = new PersonBuilder().build(); - - CommandResult commandResult = new AddCommand(validPerson).execute(modelStub); - - assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, validPerson), commandResult.getFeedbackToUser()); - assertEquals(Arrays.asList(validPerson), modelStub.personsAdded); - } - - @Test - public void execute_duplicatePerson_throwsCommandException() { - Person validPerson = new PersonBuilder().build(); - AddCommand addCommand = new AddCommand(validPerson); - ModelStub modelStub = new ModelStubWithPerson(validPerson); - - assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub)); - } - - @Test - public void equals() { - Person alice = new PersonBuilder().withName("Alice").build(); - Person bob = new PersonBuilder().withName("Bob").build(); - AddCommand addAliceCommand = new AddCommand(alice); - AddCommand addBobCommand = new AddCommand(bob); - - // same object -> returns true - assertTrue(addAliceCommand.equals(addAliceCommand)); - - // same values -> returns true - AddCommand addAliceCommandCopy = new AddCommand(alice); - assertTrue(addAliceCommand.equals(addAliceCommandCopy)); - - // different types -> returns false - assertFalse(addAliceCommand.equals(1)); - - // null -> returns false - assertFalse(addAliceCommand.equals(null)); - - // different person -> returns false - assertFalse(addAliceCommand.equals(addBobCommand)); - } - - /** - * A default model stub that have all of the methods failing. - */ - private class ModelStub implements Model { - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - throw new AssertionError("This method should not be called."); - } - - @Override - public GuiSettings getGuiSettings() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - throw new AssertionError("This method should not be called."); - } - - @Override - public Path getAddressBookFilePath() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void addPerson(Person person) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setAddressBook(ReadOnlyAddressBook newData) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - throw new AssertionError("This method should not be called."); - } - - @Override - public boolean hasPerson(Person person) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void deletePerson(Person target) { - throw new AssertionError("This method should not be called."); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - throw new AssertionError("This method should not be called."); - } - - @Override - public ObservableList<Person> getFilteredPersonList() { - throw new AssertionError("This method should not be called."); - } - - @Override - public void updateFilteredPersonList(Predicate<Person> predicate) { - throw new AssertionError("This method should not be called."); - } - } - - /** - * A Model stub that contains a single person. - */ - private class ModelStubWithPerson extends ModelStub { - private final Person person; - - ModelStubWithPerson(Person person) { - requireNonNull(person); - this.person = person; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return this.person.isSamePerson(person); - } - } - - /** - * A Model stub that always accept the person being added. - */ - private class ModelStubAcceptingPersonAdded extends ModelStub { - final ArrayList<Person> personsAdded = new ArrayList<>(); - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return personsAdded.stream().anyMatch(person::isSamePerson); - } - - @Override - public void addPerson(Person person) { - requireNonNull(person); - personsAdded.add(person); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return new AddressBook(); - } - } - -} diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java deleted file mode 100644 index 80d9110c03a..00000000000 --- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package seedu.address.logic.commands; - -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.Test; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; - -public class ClearCommandTest { - - @Test - public void execute_emptyAddressBook_success() { - Model model = new ModelManager(); - Model expectedModel = new ModelManager(); - - assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); - } - - @Test - public void execute_nonEmptyAddressBook_success() { - Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - expectedModel.setAddressBook(new AddressBook()); - - assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java deleted file mode 100644 index 214c6c2507b..00000000000 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.Test; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; -import seedu.address.testutil.PersonBuilder; - -/** - * Contains integration tests (interaction with the Model) and unit tests for EditCommand. - */ -public class EditCommandTest { - - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void execute_allFieldsSpecifiedUnfilteredList_success() { - Person editedPerson = new PersonBuilder().build(); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_someFieldsSpecifiedUnfilteredList_success() { - Index indexLastPerson = Index.fromOneBased(model.getFilteredPersonList().size()); - Person lastPerson = model.getFilteredPersonList().get(indexLastPerson.getZeroBased()); - - PersonBuilder personInList = new PersonBuilder(lastPerson); - Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withTags(VALID_TAG_HUSBAND).build(); - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) - .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build(); - EditCommand editCommand = new EditCommand(indexLastPerson, descriptor); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(lastPerson, editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_noFieldSpecifiedUnfilteredList_success() { - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor()); - Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_filteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - Person editedPerson = new PersonBuilder(personInFilteredList).withName(VALID_NAME_BOB).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, - new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); - - String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); - - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); - expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); - - assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); - } - - @Test - public void execute_duplicatePersonUnfilteredList_failure() { - Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_SECOND_PERSON, descriptor); - - assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); - } - - @Test - public void execute_duplicatePersonFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - - // edit person in filtered list into a duplicate in address book - Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, - new EditPersonDescriptorBuilder(personInList).build()); - - assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); - } - - @Test - public void execute_invalidPersonIndexUnfilteredList_failure() { - Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build(); - EditCommand editCommand = new EditCommand(outOfBoundIndex, descriptor); - - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - /** - * Edit filtered list where index is larger than size of filtered list, - * but smaller than size of address book - */ - @Test - public void execute_invalidPersonIndexFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - Index outOfBoundIndex = INDEX_SECOND_PERSON; - // ensures that outOfBoundIndex is still in bounds of address book list - assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); - - EditCommand editCommand = new EditCommand(outOfBoundIndex, - new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); - - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - @Test - public void equals() { - final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY); - - // same values -> returns true - EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY); - EditCommand commandWithSameValues = new EditCommand(INDEX_FIRST_PERSON, copyDescriptor); - assertTrue(standardCommand.equals(commandWithSameValues)); - - // same object -> returns true - assertTrue(standardCommand.equals(standardCommand)); - - // null -> returns false - assertFalse(standardCommand.equals(null)); - - // different types -> returns false - assertFalse(standardCommand.equals(new ClearCommand())); - - // different index -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND_PERSON, DESC_AMY))); - - // different descriptor -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST_PERSON, DESC_BOB))); - } - -} diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java deleted file mode 100644 index 9b15db28bbb..00000000000 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.CARL; -import static seedu.address.testutil.TypicalPersons.ELLE; -import static seedu.address.testutil.TypicalPersons.FIONA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.util.Arrays; -import java.util.Collections; - -import org.junit.jupiter.api.Test; - -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Contains integration tests (interaction with the Model) for {@code FindCommand}. - */ -public class FindCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void equals() { - NameContainsKeywordsPredicate firstPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("first")); - NameContainsKeywordsPredicate secondPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("second")); - - FindCommand findFirstCommand = new FindCommand(firstPredicate); - FindCommand findSecondCommand = new FindCommand(secondPredicate); - - // same object -> returns true - assertTrue(findFirstCommand.equals(findFirstCommand)); - - // same values -> returns true - FindCommand findFirstCommandCopy = new FindCommand(firstPredicate); - assertTrue(findFirstCommand.equals(findFirstCommandCopy)); - - // different types -> returns false - assertFalse(findFirstCommand.equals(1)); - - // null -> returns false - assertFalse(findFirstCommand.equals(null)); - - // different person -> returns false - assertFalse(findFirstCommand.equals(findSecondCommand)); - } - - @Test - public void execute_zeroKeywords_noPersonFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); - NameContainsKeywordsPredicate predicate = preparePredicate(" "); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredPersonList(predicate); - assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Collections.emptyList(), model.getFilteredPersonList()); - } - - @Test - public void execute_multipleKeywords_multiplePersonsFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); - NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz"); - FindCommand command = new FindCommand(predicate); - expectedModel.updateFilteredPersonList(predicate); - assertCommandSuccess(command, model, expectedMessage, expectedModel); - assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList()); - } - - /** - * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. - */ - private NameContainsKeywordsPredicate preparePredicate(String userInput) { - return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+"))); - } -} diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java deleted file mode 100644 index 435ff1f7275..00000000000 --- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package seedu.address.logic.commands; - -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; - -/** - * Contains integration tests (interaction with the Model) and unit tests for ListCommand. - */ -public class ListCommandTest { - - private Model model; - private Model expectedModel; - - @BeforeEach - public void setUp() { - model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); - } - - @Test - public void execute_listIsNotFiltered_showsSameList() { - assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel); - } - - @Test - public void execute_listIsFiltered_showsEverything() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel); - } -} diff --git a/src/test/java/seedu/address/logic/general/ClearCommandTest.java b/src/test/java/seedu/address/logic/general/ClearCommandTest.java new file mode 100644 index 00000000000..c530dd9d34a --- /dev/null +++ b/src/test/java/seedu/address/logic/general/ClearCommandTest.java @@ -0,0 +1,31 @@ +package seedu.address.logic.general; + +import static seedu.address.logic.general.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.HrManager; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +public class ClearCommandTest { + + @Test + public void execute_emptyHrManager_success() { + Model model = new ModelManager(); + Model expectedModel = new ModelManager(); + + assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_nonEmptyHrManager_success() { + Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalHrManager(), new UserPrefs()); + expectedModel.setHrManager(new HrManager()); + + assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/general/CommandTestUtil.java b/src/test/java/seedu/address/logic/general/CommandTestUtil.java new file mode 100644 index 00000000000..5445cd05aaf --- /dev/null +++ b/src/test/java/seedu/address/logic/general/CommandTestUtil.java @@ -0,0 +1,38 @@ +package seedu.address.logic.general; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; + +public class CommandTestUtil { + + /** + * Executes the given {@code command}, confirms that <br> + * - the returned {@link CommandResult} matches {@code expectedCommandResult} <br> + * - the {@code actualModel} matches {@code expectedModel} + */ + public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, + Model expectedModel) { + try { + CommandResult result = command.execute(actualModel); + assertEquals(expectedCommandResult, result); + assertEquals(expectedModel, actualModel); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} + * that takes a string {@code expectedMessage}. + */ + public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + CommandResult expectedCommandResult = new CommandResult(expectedMessage, CommandResult.CommandType.GENERAL); + assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); + } + +} diff --git a/src/test/java/seedu/address/logic/interview/AddInterviewCommandTest.java b/src/test/java/seedu/address/logic/interview/AddInterviewCommandTest.java new file mode 100644 index 00000000000..16562d122b0 --- /dev/null +++ b/src/test/java/seedu/address/logic/interview/AddInterviewCommandTest.java @@ -0,0 +1,344 @@ +package seedu.address.logic.interview; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static seedu.address.model.position.Position.MESSAGE_POSITION_CLOSED; +import static seedu.address.model.position.Position.MESSAGE_POSITION_DOES_NOT_EXIST; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; +import static seedu.address.testutil.TypicalPositions.HR_MANAGER; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.HrManager; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; +import seedu.address.testutil.InterviewBuilder; +import seedu.address.testutil.ModelStub; +import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.PositionBuilder; + +class AddInterviewCommandTest { + + @Test + public void execute_interviewAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingInterviewAdded modelStub = new ModelStubAcceptingInterviewAdded(); + Interview validInterview = new InterviewBuilder().build(); + Set<Index> indexSet = new HashSet<>(); + indexSet.add(INDEX_FIRST_PERSON); + CommandResult commandResult = new AddInterviewCommand(validInterview, indexSet).execute(modelStub); + + assertEquals(String.format(AddInterviewCommand.MESSAGE_SUCCESS, validInterview.getDisplayString()), + commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(validInterview), modelStub.interviewsAdded); + } + + @Test + public void constructor_nullInterview_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new AddInterviewCommand(null, null)); + } + + @Test + public void execute_duplicateInterview_throwsCommandException() { + Interview validInterview = new InterviewBuilder().build(); + AddInterviewCommand addInterviewCommand = new AddInterviewCommand(validInterview, new HashSet<>()); + ModelStubWithInterviewAndPosition modelStub = new ModelStubWithInterviewAndPosition(validInterview); + assertThrows(CommandException.class, AddInterviewCommand.MESSAGE_DUPLICATE_INTERVIEW, () -> + addInterviewCommand.execute(modelStub)); + } + + @Test + public void execute_noPositionFound_throwsCommandException() { + Interview validInterview = new InterviewBuilder().build(); + AddInterviewCommand addInterviewCommand = new AddInterviewCommand(validInterview, new HashSet<>()); + ModelStubWithNoPosition modelStub = new ModelStubWithNoPosition(); + + assertThrows(CommandException.class, String.format(MESSAGE_POSITION_DOES_NOT_EXIST, + validInterview.getPosition().getTitle()), () -> addInterviewCommand.execute(modelStub)); + } + + @Test + public void execute_invalidPersonIndex_throwsCommandException() { + ModelStubWithObservable model = new ModelStubWithObservable(); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + Set<Index> temp = new HashSet<>(); + temp.add(outOfBoundIndex); + Interview validInterview = new InterviewBuilder().withPosition(ADMIN_ASSISTANT).build(); + AddInterviewCommand addInterviewCommand = new AddInterviewCommand(validInterview, temp); + + assertThrows(CommandException.class, + Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, () -> addInterviewCommand.execute(model)); + } + + @Test + public void execute_noSuchPosition_throwsCommandException() { + ModelStubWithObservable model = new ModelStubWithObservable(); + Index index = Index.fromZeroBased(0); + Set<Index> temp = new HashSet<>(); + temp.add(index); + Interview interview = new InterviewBuilder() + .withPosition(new Position(new Title("accountant"))).build(); + AddInterviewCommand addInterviewCommand = new AddInterviewCommand(interview, temp); + + assertThrows(CommandException.class, String.format(MESSAGE_POSITION_DOES_NOT_EXIST, + "accountant"), () -> addInterviewCommand.execute(model)); + } + + @Test + public void execute_didNotApply_throwsCommandException() { + ModelStubWithObservable model = new ModelStubWithObservable(); + Index index = Index.fromZeroBased(0); + Set<Index> temp = new HashSet<>(); + temp.add(index); + Set<Person> candidates = new HashSet<>(); + candidates.add(BENSON); + Interview interview = new InterviewBuilder().withCandidates(candidates) + .withPosition(ADMIN_ASSISTANT).build(); + AddInterviewCommand addInterviewCommand = new AddInterviewCommand(interview, temp); + + assertThrows(CommandException.class, String.format(AddInterviewCommand.MESSAGE_CANDIDATE_DID_NOT_APPLY, + BENSON.getName(), ADMIN_ASSISTANT), () -> addInterviewCommand.execute(model)); + } + + @Test + public void execute_positionClosed_throwsCommandException() { + ModelStubWithObservable model = new ModelStubWithObservable(); + Index index = Index.fromZeroBased(0); + Set<Index> temp = new HashSet<>(); + temp.add(index); + Set<Person> candidates = new HashSet<>(); + Person person = new PersonBuilder(BENSON).withPositions(ADMIN_ASSISTANT.getTitle().fullTitle) + .build(); + candidates.add(person); + + Interview validInterview = new InterviewBuilder().withCandidates(candidates) + .withPosition( + new PositionBuilder().withTitle("Admin") + .withStatus(Position.PositionStatus.CLOSED).build()) + .build(); + AddInterviewCommand addInterviewCommand = new AddInterviewCommand(validInterview, temp); + + assertThrows(CommandException.class, String.format(MESSAGE_POSITION_CLOSED, + "Admin"), () -> addInterviewCommand.execute(model)); + } + + @Test + public void equals() { + Interview validInterview = new InterviewBuilder().build(); + AddInterviewCommand addInterviewCommand = new AddInterviewCommand(validInterview, new HashSet<>()); + Interview otherValidInterview = new Interview(CommandTestUtil.VALID_POSITION_ADMIN, + CommandTestUtil.VALID_CANDIDATES_SET, CommandTestUtil.VALID_LOCAL_DATE, + CommandTestUtil.VALID_START_TIME, CommandTestUtil.VALID_DURATION, + CommandTestUtil.VALID_STATUS_PENDING); + AddInterviewCommand otherAddInterviewCommand = new AddInterviewCommand(otherValidInterview, new HashSet<>()); + assertFalse(otherAddInterviewCommand.equals(addInterviewCommand)); + } + + /** + * A Model stub that contains a single interview. + */ + private class ModelStubWithInterviewAndPosition extends ModelStub { + private final Interview interview; + + ModelStubWithInterviewAndPosition(Interview interview) { + requireNonNull(interview); + this.interview = interview; + } + + @Override + public boolean hasPosition(Position position) { + return true; + } + + @Override + public Position getPositionReference(Position position) { + return position; + } + + @Override + public boolean hasInterview(Interview interview) { + requireNonNull(interview); + return this.interview.isSameInterview(interview); + } + + @Override + public boolean isPositionClosed(Position toCheck) { + return false; + } + + @Override + public ObservableList<Person> getFilteredPersonList() { + return FXCollections.observableArrayList(); + } + } + + /** + * A Model stub that has ObservableList fields. + */ + private class ModelStubWithObservable extends ModelStub { + private ObservableList<Person> emptyPersonList = FXCollections.observableArrayList(); + private ObservableList<Position> emptyPositionList = FXCollections.observableArrayList(); + + ModelStubWithObservable() { + emptyPersonList.add(BENSON); + emptyPositionList.add(ADMIN_ASSISTANT); + emptyPositionList.add(HR_MANAGER); + Position closedPosition = new PositionBuilder().withTitle("Admin") + .withStatus(Position.PositionStatus.CLOSED).build(); + emptyPositionList.add(closedPosition); + } + + @Override + public ObservableList<Position> getFilteredPositionList() { + return emptyPositionList; + } + + @Override + public ObservableList<Person> getFilteredPersonList() { + return emptyPersonList; + } + + @Override + public Person getPerson(Index index) { + return emptyPersonList.get(index.getZeroBased()); + } + + @Override + public boolean hasPosition(Position position) { + return emptyPositionList.contains(position); + } + + @Override + public Position getPositionReference(Position position) { + return position; + } + + @Override + public boolean isPositionClosed(Position toCheck) { + for (Position p : emptyPositionList) { + if (p.isSamePosition(toCheck)) { + return p.getStatus() == Position.PositionStatus.CLOSED; + } + } + return false; + } + + @Override + public boolean hasInterview(Interview toAdd) { + return false; + } + + @Override + public void addInterview(Interview toAdd) { + + } + + @Override + public void updateFilteredPersonList(Predicate<Person> predicate) { + + } + } + + /** + * A Model stub that contains no Position. + */ + private class ModelStubWithNoPosition extends ModelStub { + + ModelStubWithNoPosition() { + } + + @Override + public boolean hasPosition(Position position) { + return false; + } + + @Override + public boolean hasInterview(Interview toAdd) { + return false; + } + + @Override + public ObservableList<Person> getFilteredPersonList() { + return FXCollections.observableArrayList(); + } + } + + /** + * A Model stub that always accept the interview being added. + */ + private class ModelStubAcceptingInterviewAdded extends ModelStub { + final ArrayList<Interview> interviewsAdded = new ArrayList<>(); + private ObservableList<Person> personsList = FXCollections.observableArrayList(); + private ObservableList<Position> positionsList = FXCollections.observableArrayList(); + + private ModelStubAcceptingInterviewAdded() { + personsList.add(BENSON); + } + + @Override + public Person getPerson(Index index) { + requireNonNull(index); + return personsList.get(index.getZeroBased()); + } + + @Override + public boolean hasPosition(Position position) { + return true; + } + + @Override + public Position getPositionReference(Position position) { + return position; + } + + @Override + public boolean hasInterview(Interview interview) { + requireNonNull(interview); + return interviewsAdded.stream().anyMatch(interview::isSameInterview); + } + + @Override + public void updateFilteredPersonList(Predicate<Person> predicate) { + //empty method that does nothing for the stub to always accept + } + + @Override + public void addInterview(Interview interview) { + requireNonNull(interview); + interviewsAdded.add(interview); + } + + @Override + public ObservableList<Person> getFilteredPersonList() { + return personsList; + } + + @Override + public boolean isPositionClosed(Position toCheck) { + return false; + } + + @Override + public ReadOnlyHrManager getHrManager() { + return new HrManager(); + } + } +} diff --git a/src/test/java/seedu/address/logic/interview/AssignInterviewCommandTest.java b/src/test/java/seedu/address/logic/interview/AssignInterviewCommandTest.java new file mode 100644 index 00000000000..aa27120c168 --- /dev/null +++ b/src/test/java/seedu/address/logic/interview/AssignInterviewCommandTest.java @@ -0,0 +1,146 @@ +package seedu.address.logic.interview; + +import static seedu.address.logic.interview.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.interview.CommandTestUtil.assertCommandFailureRepeatedAssign; +import static seedu.address.logic.interview.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_INTERVIEW; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_INTERVIEW; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandResult; +import seedu.address.model.HrManager; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; +import seedu.address.testutil.InterviewBuilder; +import seedu.address.testutil.PersonBuilder; + +public class AssignInterviewCommandTest { + + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Set<Index> indexes = new HashSet<>(); + indexes.add(INDEX_FIRST_PERSON); + AssignInterviewCommand assignInterviewCommand = new AssignInterviewCommand(INDEX_FIRST_INTERVIEW, + indexes); + Interview assignedInterview = new InterviewBuilder() + .withPosition(new Position(new Title("Accountant"))) + .withCandidates(new HashSet<>()) + .withDate(LocalDate.of(2021, 10, 15)) + .withStartTime(LocalTime.of(14, 0)) + .withDuration(Duration.ofMinutes(120)) + .withStatus(Interview.InterviewStatus.PENDING).build(); + + Person alice = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person copyOfAlice = new PersonBuilder() + .withName("Alice Pauline") + .withPositions("Accountant") + .withAddress("123, Jurong West Ave 6, #08-111") + .withEmail("alice@example.com") + .withPhone("94351253") + .withRemark("She likes aardvarks.") + .withTags("friends") + .withStatus("Applied").build(); + + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("1. " + alice.getName() + "\n"); + + String expectedMessage = String.format(AssignInterviewCommand.MESSAGE_SUCCESS, + assignedInterview.getDisplayStringWithoutNames(), sb); + + copyOfAlice.addInterview(assignedInterview); + assignedInterview.addCandidate(copyOfAlice); + + CommandResult expectedCommandResult = new CommandResult(expectedMessage, CommandResult.CommandType.INTERVIEW); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + assertCommandSuccess(assignInterviewCommand, model, expectedCommandResult, expectedModel); + + alice.deleteInterview(assignedInterview); + assignedInterview.deleteCandidate(alice); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Set<Index> indexes = new HashSet<>(); + indexes.add(INDEX_FIRST_PERSON); + + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredInterviewList().size() + 1); + AssignInterviewCommand assignInterviewCommand = new AssignInterviewCommand(outOfBoundIndex, indexes); + + assertCommandFailure(assignInterviewCommand, model, Messages.MESSAGE_INVALID_INTERVIEW_DISPLAYED_INDEX); + } + + @Test + public void execute_candidateUnappliedUnfilteredList_throwsCommandException() { + Set<Index> indexes = new HashSet<>(); + indexes.add(INDEX_FIRST_PERSON); + + Interview interviewToAssign = model.getFilteredInterviewList().get(INDEX_SECOND_INTERVIEW.getZeroBased()); + + Person alice = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + + AssignInterviewCommand assignInterviewCommand = new AssignInterviewCommand(INDEX_SECOND_INTERVIEW, + indexes); + + String expectedMessage = String.format(AssignInterviewCommand.MESSAGE_CANDIDATE_DID_NOT_APPLY, + INDEX_FIRST_PERSON.getOneBased(), alice.getName(), interviewToAssign.getPositionTitle()); + + assertCommandFailure(assignInterviewCommand, model, expectedMessage); + } + + @Test + public void execute_candidateHasInterviewUnfilteredList_throwsCommandException() { + Set<Index> indexes = new HashSet<>(); + indexes.add(INDEX_FIRST_PERSON); + AssignInterviewCommand assignInterviewCommand = new AssignInterviewCommand(INDEX_FIRST_INTERVIEW, + indexes); + Interview assignedInterview = new InterviewBuilder() + .withPosition(new Position(new Title("Accountant"))) + .withCandidates(new HashSet<>()) + .withDate(LocalDate.of(2021, 10, 15)) + .withStartTime(LocalTime.of(14, 0)) + .withDuration(Duration.ofMinutes(120)) + .withStatus(Interview.InterviewStatus.PENDING).build(); + + Person alice = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person copyOfAlice = new PersonBuilder() + .withName("Alice Pauline") + .withPositions("Accountant") + .withAddress("123, Jurong West Ave 6, #08-111") + .withEmail("alice@example.com") + .withPhone("94351253") + .withRemark("She likes aardvarks.") + .withTags("friends") + .withStatus("Applied").build(); + + String expectedMessage = String.format(AssignInterviewCommand.MESSAGE_CANDIDATE_HAS_INTERVIEW, + INDEX_FIRST_PERSON.getOneBased(), alice.getName(), assignedInterview.getDisplayStringWithoutNames()); + + copyOfAlice.addInterview(assignedInterview); + assignedInterview.addCandidate(copyOfAlice); + + assertCommandFailureRepeatedAssign(assignInterviewCommand, model, expectedMessage); + + alice.deleteInterview(assignedInterview); + assignedInterview.deleteCandidate(alice); + } +} diff --git a/src/test/java/seedu/address/logic/interview/CommandTestUtil.java b/src/test/java/seedu/address/logic/interview/CommandTestUtil.java new file mode 100644 index 00000000000..e40bc1ff278 --- /dev/null +++ b/src/test/java/seedu/address/logic/interview/CommandTestUtil.java @@ -0,0 +1,230 @@ +package seedu.address.logic.interview; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.BOB; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.HrManager; +import seedu.address.model.Model; +import seedu.address.model.interview.Interview; +import seedu.address.model.interview.Interview.InterviewStatus; +import seedu.address.model.interview.PositionTitleContainsKeywordsPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; +import seedu.address.testutil.EditInterviewDescriptorBuilder; + +public class CommandTestUtil { + + public static final Position VALID_POSITION_ADMIN = new Position(new Title("Admin")); + public static final Position VALID_POSITION_MANAGER = new Position(new Title("Manager")); + public static final HashSet<Person> VALID_CANDIDATES_SET = new HashSet<>(List.of(ALICE, BOB)); + public static final HashSet<Index> VALID_EMPTY_CANDIDATE_INDEX_SET = new HashSet<>(); + public static final HashSet<Index> VALID_CANDIDATE_INDEX_SET = + new HashSet<>(List.of(Index.fromZeroBased(1), Index.fromZeroBased(2), Index.fromZeroBased(3))); + public static final LocalDate VALID_LOCAL_DATE = LocalDate.of(2021, 10, 18); + public static final LocalDate VALID_LOCAL_DATE_OTHER_DATE = LocalDate.of(2021, 10, 01); + public static final LocalTime VALID_START_TIME = LocalTime.NOON; //12:00 + public static final LocalTime VALID_START_TIME_OTHER_START_TIME = LocalTime.MIDNIGHT; //00:00 + public static final Duration VALID_DURATION = Duration.ofMinutes(180); + public static final Duration VALID_DURATION_OTHER_DURATION = Duration.ofMinutes(120); + public static final InterviewStatus VALID_STATUS_PENDING = InterviewStatus.PENDING; + public static final InterviewStatus VALID_STATUS_COMPLETED = InterviewStatus.COMPLETED; + + public static final String PREAMBLE_WHITESPACE = "\t \r \n"; + public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; + + public static final String VALID_NAME_ALICE = ALICE.getName().fullName; + public static final String VALID_NAME_BOB = BOB.getName().fullName; + public static final String VALID_DATE = "18/10/2021"; + public static final String VALID_DATE_OTHER_DATE = "21/10/2021"; + public static final String VALID_TIME = "1200"; + public static final String VALID_TIME_OTHER_TIME = "1300"; + public static final String VALID_DURATION_TIME = "180"; + public static final String VALID_DURATION_TIME_OTHER_DURATION = "190"; + public static final String VALID_CANDIDATE_INDEX_1 = "1"; + public static final String VALID_CANDIDATE_INDEX_2 = "2"; + public static final String VALID_POSITION_ADMIN_ASSISTANT = "Administrative Assistant"; + + public static final String VALID_POSITION_ADMIN_DESC = " " + PREFIX_POSITION + VALID_POSITION_ADMIN.getTitle(); + public static final String VALID_POSITION_MANAGER_DESC = " " + PREFIX_POSITION + VALID_POSITION_MANAGER.getTitle(); + public static final String VALID_POSITION_ADMIN_ASST_DESC = " " + PREFIX_POSITION + VALID_POSITION_ADMIN_ASSISTANT; + public static final String VALID_CANDIDATE_DESC_ALICE = " " + PREFIX_CANDIDATE_INDEX + "1"; + public static final String VALID_CANDIDATE_DESC_BOB = " " + PREFIX_CANDIDATE_INDEX + "2"; + public static final String VALID_DATE_DESC = " " + PREFIX_DATE + VALID_DATE; + public static final String VALID_DATE_OTHER_DATE_DESC = " " + PREFIX_DATE + VALID_DATE_OTHER_DATE; + public static final String VALID_TIME_DESC = " " + PREFIX_TIME + VALID_TIME; + public static final String VALID_TIME_OTHER_TIME_DESC = " " + PREFIX_TIME + VALID_TIME_OTHER_TIME; + public static final String VALID_DURATION_DESC = " " + PREFIX_DURATION + VALID_DURATION_TIME; + public static final String VALID_DURATION_TIME_OTHER_DURATION_DESC = " " + PREFIX_DURATION + + VALID_DURATION_TIME_OTHER_DURATION; + public static final String VALID_STATUS_PENDING_DESC = " " + PREFIX_INTERVIEW_STATUS + VALID_STATUS_PENDING; + public static final String VALID_STATUS_COMPLETED_DESC = " " + PREFIX_INTERVIEW_STATUS + + VALID_STATUS_COMPLETED; + + public static final String VALID_POSITION_ADMIN_NAME = VALID_POSITION_ADMIN.getTitle().toString(); + public static final String VALID_POSITION_MANAGER_NAME = VALID_POSITION_MANAGER.getTitle().toString(); + + public static final String INVALID_POSITION_DESC = " " + PREFIX_POSITION + "@123"; + public static final String INVALID_DATE_DESC = " " + PREFIX_DATE + "38/10/2021"; + public static final String INVALID_TIME_DESC = " " + PREFIX_TIME + "2500"; + public static final String INVALID_DURATION_TIME = " " + PREFIX_DURATION + "-180"; + public static final String INVALID_INDEX_DESC = " " + PREFIX_CANDIDATE_INDEX + "?"; + public static final String INVALID_STATUS_DESC = " " + PREFIX_INTERVIEW_STATUS + "blah"; + + public static final EditInterviewCommand.EditInterviewDescriptor DESC_INTERVIEW_ADMIN_ASSISTANT; + public static final EditInterviewCommand.EditInterviewDescriptor DESC_INTERVIEW_MANAGER; + + static { + DESC_INTERVIEW_ADMIN_ASSISTANT = new EditInterviewDescriptorBuilder().withPosition(VALID_POSITION_ADMIN_NAME) + .withDate(VALID_DATE).withStartTime(VALID_TIME) + .withDuration(VALID_DURATION_TIME).withStatus(VALID_STATUS_PENDING).build(); + DESC_INTERVIEW_MANAGER = new EditInterviewDescriptorBuilder().withPosition(VALID_POSITION_MANAGER_NAME) + .withDate(VALID_DATE).withStartTime(VALID_TIME) + .withDuration(VALID_DURATION_TIME_OTHER_DURATION).withStatus(VALID_STATUS_PENDING).build(); + } + + /** + * Executes the given {@code command}, confirms that <br> + * - the returned {@link CommandResult} matches {@code expectedCommandResult} <br> + * - the {@code actualModel} matches {@code expectedModel} + */ + public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, + Model expectedModel) { + try { + CommandResult result = command.execute(actualModel); + assertEquals(expectedCommandResult, result); + assertEquals(expectedModel, actualModel); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} + * that takes a string {@code expectedMessage}. + */ + public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + CommandResult expectedCommandResult = new CommandResult(expectedMessage, CommandResult.CommandType.INTERVIEW); + assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); + } + + /** + * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} + * that takes a string {@code expectedMessage}. + */ + public static void assertListCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + CommandResult expectedCommandResult = new CommandResult(expectedMessage, CommandResult.CommandType.LIST_I); + assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); + } + + private static boolean isEditICommand(Command command) { + String commandClassName = command.getClass().getSimpleName(); + return commandClassName.equals("EditInterviewCommand"); + } + + /** + * Executes the given {@code command}, confirms that <br> + * - the given {@code command} is an edit command <br> + * - the returned {@link CommandResult} matches {@code expectedCommandResult} <br> + * - the {@code actualModel} matches {@code expectedModel} + */ + public static void assertEditInterviewCommandSuccess(Command command, Model actualModel, + CommandResult expectedEditCommandResult, Model expectedModel) { + try { + CommandResult result = command.execute(actualModel); + assertEquals(expectedEditCommandResult, result); + assertEquals(expectedModel, actualModel); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Convenience wrapper to {@link #assertEditInterviewCommandSuccess(Command, Model, CommandResult, Model)} + * that takes a string {@code expectedMessage}. + */ + public static void assertEditInterviewCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + if (!isEditICommand(command)) { + throw new AssertionError("Command should be an EditInterviewCommand"); + } + + CommandResult expectedCommandResult = new CommandResult(expectedMessage, + CommandResult.CommandType.INTERVIEW); + assertEditInterviewCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); + } + + /** + * Executes the given {@code command}, confirms that <br> + * - a {@code CommandException} is thrown <br> + * - the CommandException message matches {@code expectedMessage} <br> + * - the HR Manager, filtered interview list and selected interview in {@code actualModel} remain unchanged + */ + public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) { + // we are unable to defensively copy the model for comparison later, so we can + // only do so by copying its components. + HrManager expectedHrManager = new HrManager(actualModel.getHrManager()); + List<Interview> expectedFilteredList = new ArrayList<>(actualModel.getFilteredInterviewList()); + + assertThrows(CommandException.class, expectedMessage, () -> command.execute(actualModel)); + assertEquals(expectedHrManager, actualModel.getHrManager()); + assertEquals(expectedFilteredList, actualModel.getFilteredInterviewList()); + } + + /** + * Executes the given {@code command} twice, confirms that <br> + * - a {@code CommandException} is thrown <br> + * - the CommandException message matches {@code expectedMessage} <br> + * - the HR Manager, filtered interview list and selected interview in {@code actualModel} remain unchanged + */ + public static void assertCommandFailureRepeatedAssign(Command command, Model actualModel, String expectedMessage) { + HrManager expectedHrManager = new HrManager(actualModel.getHrManager()); + List<Interview> expectedFilteredList = new ArrayList<>(actualModel.getFilteredInterviewList()); + + try { + command.execute(actualModel); + assertThrows(CommandException.class, expectedMessage, () -> command.execute(actualModel)); + assertEquals(expectedHrManager, actualModel.getHrManager()); + assertEquals(expectedFilteredList, actualModel.getFilteredInterviewList()); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Updates {@code model}'s filtered list to show only the interview at the given {@code targetIndex} in the + * {@code model}'s HR Manager. + */ + public static void showInterviewAtIndex(Model model, Index targetIndex) { + assertTrue(targetIndex.getZeroBased() < model.getFilteredInterviewList().size()); + + Interview interview = model.getFilteredInterviewList().get(targetIndex.getZeroBased()); + final String[] splitTitle = interview.getPositionTitle().fullTitle.split("\\s+"); + model.updateFilteredInterviewList(new PositionTitleContainsKeywordsPredicate(Arrays.asList(splitTitle[0]))); + + assertEquals(1, model.getFilteredInterviewList().size()); + } +} diff --git a/src/test/java/seedu/address/logic/interview/DeleteInterviewCommandTest.java b/src/test/java/seedu/address/logic/interview/DeleteInterviewCommandTest.java new file mode 100644 index 00000000000..0cb83c4e55e --- /dev/null +++ b/src/test/java/seedu/address/logic/interview/DeleteInterviewCommandTest.java @@ -0,0 +1,105 @@ +package seedu.address.logic.interview; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.interview.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.interview.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.interview.CommandTestUtil.showInterviewAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_INTERVIEW; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_INTERVIEW; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.interview.Interview; + +public class DeleteInterviewCommandTest { + + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Interview interviewToDelete = model.getFilteredInterviewList().get(INDEX_FIRST_INTERVIEW.getZeroBased()); + DeleteInterviewCommand deleteInterviewCommand = new DeleteInterviewCommand(INDEX_FIRST_INTERVIEW); + + String expectedMessage = String.format(DeleteInterviewCommand.MESSAGE_DELETE_INTERVIEW_SUCCESS, + interviewToDelete.getDisplayString()); + + ModelManager expectedModel = new ModelManager(model.getHrManager(), new UserPrefs()); + expectedModel.deleteInterview(interviewToDelete); + + assertCommandSuccess(deleteInterviewCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredInterviewList().size() + 1); + DeleteInterviewCommand deleteInterviewCommand = new DeleteInterviewCommand(outOfBoundIndex); + + assertCommandFailure(deleteInterviewCommand, model, Messages.MESSAGE_INVALID_INTERVIEW_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexFilteredList_success() { + showInterviewAtIndex(model, INDEX_FIRST_INTERVIEW); + + Interview interviewToDelete = model.getFilteredInterviewList().get(INDEX_FIRST_INTERVIEW.getZeroBased()); + DeleteInterviewCommand deleteInterviewCommand = new DeleteInterviewCommand(INDEX_FIRST_INTERVIEW); + + String expectedMessage = String.format(DeleteInterviewCommand.MESSAGE_DELETE_INTERVIEW_SUCCESS, + interviewToDelete.getDisplayString()); + Model expectedModel = new ModelManager(getTypicalHrManager(), new UserPrefs()); + expectedModel.deleteInterview(interviewToDelete); + showNoInterview(expectedModel); + assertCommandSuccess(deleteInterviewCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showInterviewAtIndex(model, INDEX_FIRST_INTERVIEW); + + Index outOfBoundIndex = INDEX_SECOND_INTERVIEW; + // ensures that outOfBoundIndex is still in bounds of HrManager's list + assertTrue(outOfBoundIndex.getZeroBased() < model.getHrManager().getInterviewList().size()); + + DeleteInterviewCommand deleteInterviewCommand = new DeleteInterviewCommand(outOfBoundIndex); + + assertCommandFailure(deleteInterviewCommand, model, Messages.MESSAGE_INVALID_INTERVIEW_DISPLAYED_INDEX); + } + + @Test + public void equals() { + DeleteInterviewCommand deleteFirstCommand = new DeleteInterviewCommand(INDEX_FIRST_INTERVIEW); + DeleteInterviewCommand deleteSecondCommand = new DeleteInterviewCommand(INDEX_SECOND_INTERVIEW); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + DeleteInterviewCommand deleteFirstCommandCopy = new DeleteInterviewCommand(INDEX_FIRST_INTERVIEW); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different position -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } + + /** + * Updates {@code model}'s filtered list to show no one. + */ + private void showNoInterview(Model model) { + model.updateFilteredInterviewList(i -> false); + assertTrue(model.getFilteredInterviewList().isEmpty()); + } + +} diff --git a/src/test/java/seedu/address/logic/interview/EditInterviewCommandTest.java b/src/test/java/seedu/address/logic/interview/EditInterviewCommandTest.java new file mode 100644 index 00000000000..c0105283eb0 --- /dev/null +++ b/src/test/java/seedu/address/logic/interview/EditInterviewCommandTest.java @@ -0,0 +1,261 @@ +package seedu.address.logic.interview; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.interview.CommandTestUtil.DESC_INTERVIEW_ADMIN_ASSISTANT; +import static seedu.address.logic.interview.CommandTestUtil.DESC_INTERVIEW_MANAGER; +import static seedu.address.logic.interview.CommandTestUtil.VALID_POSITION_ADMIN_NAME; +import static seedu.address.logic.interview.CommandTestUtil.VALID_POSITION_MANAGER_NAME; +import static seedu.address.logic.interview.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.interview.CommandTestUtil.assertEditInterviewCommandSuccess; +import static seedu.address.logic.interview.CommandTestUtil.showInterviewAtIndex; +import static seedu.address.model.position.Position.MESSAGE_POSITION_CLOSED; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_INTERVIEW; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_INTERVIEW; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.JOHN; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; +import static seedu.address.testutil.TypicalPositions.BOOKKEEPER; +import static seedu.address.testutil.TypicalPositions.CLOSED_POSITION_CLERK; + +import java.time.LocalDate; +import java.util.Set; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.logic.general.ClearCommand; +import seedu.address.logic.interview.EditInterviewCommand.EditInterviewDescriptor; +import seedu.address.model.HrManager; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; +import seedu.address.testutil.EditInterviewDescriptorBuilder; +import seedu.address.testutil.InterviewBuilder; +import seedu.address.testutil.ModelStub; + + +/** + * Contains integration tests (interaction with the Model) and unit tests for EditInterviewCommand. + */ +public class EditInterviewCommandTest { + + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void execute_allFieldsSpecifiedUnfilteredList_success() { //todo + Interview editedInterview = new InterviewBuilder().build(); + EditInterviewDescriptor descriptor = + new EditInterviewDescriptorBuilder(editedInterview).build(); + EditInterviewCommand editInterviewCommand = new EditInterviewCommand(INDEX_FIRST_INTERVIEW, descriptor); + + String expectedMessage = + String.format(EditInterviewCommand.MESSAGE_EDIT_INTERVIEW_SUCCESS, editedInterview.getDisplayString()); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + expectedModel.setInterview(model.getFilteredInterviewList().get(0), editedInterview); + + assertEditInterviewCommandSuccess(editInterviewCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_noFieldSpecifiedUnfilteredList_success() { //todo + EditInterviewCommand editInterviewCommand = new EditInterviewCommand(INDEX_FIRST_INTERVIEW, + new EditInterviewCommand.EditInterviewDescriptor()); + + Interview editedInterview = model.getFilteredInterviewList().get(INDEX_FIRST_INTERVIEW.getZeroBased()); + + String expectedMessage = + String.format(EditInterviewCommand.MESSAGE_EDIT_INTERVIEW_SUCCESS, editedInterview.getDisplayString()); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + + assertEditInterviewCommandSuccess(editInterviewCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_filteredList_success() { //todo + showInterviewAtIndex(model, INDEX_FIRST_INTERVIEW); + + Interview interviewInFilteredList = model.getFilteredInterviewList().get(INDEX_FIRST_INTERVIEW.getZeroBased()); + + Interview editedInterview = + new InterviewBuilder(interviewInFilteredList) + .withDate(LocalDate.of(2021, 7, 28)).build(); + + EditInterviewCommand editInterviewCommand = new EditInterviewCommand(INDEX_FIRST_INTERVIEW, + new EditInterviewDescriptorBuilder().withDate("28/7/2021").build()); + + String expectedMessage = + String.format(EditInterviewCommand.MESSAGE_EDIT_INTERVIEW_SUCCESS, editedInterview.getDisplayString()); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + expectedModel.setInterview(model.getFilteredInterviewList().get(0), editedInterview); + showInterviewAtIndex(expectedModel, INDEX_FIRST_INTERVIEW); + + assertEditInterviewCommandSuccess(editInterviewCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_duplicateInterviewUnfilteredList_failure() { + Interview firstInterview = model.getFilteredInterviewList().get(INDEX_FIRST_INTERVIEW.getZeroBased()); + EditInterviewCommand.EditInterviewDescriptor descriptor = + new EditInterviewDescriptorBuilder(firstInterview).build(); + EditInterviewCommand editInterviewCommand = new EditInterviewCommand(INDEX_SECOND_INTERVIEW, descriptor); + + assertCommandFailure(editInterviewCommand, model, EditInterviewCommand.MESSAGE_DUPLICATE_INTERVIEW); + } + + @Test + public void execute_duplicateInterviewFilteredList_failure() { + showInterviewAtIndex(model, INDEX_FIRST_INTERVIEW); + + // edit interview in filtered list into a duplicate in HR Manager + Interview interviewInList = model.getHrManager().getInterviewList().get(INDEX_SECOND_INTERVIEW.getZeroBased()); + EditInterviewCommand editInterviewCommand = new EditInterviewCommand(INDEX_FIRST_INTERVIEW, + new EditInterviewDescriptorBuilder(interviewInList).build()); + + assertCommandFailure(editInterviewCommand, model, EditInterviewCommand.MESSAGE_DUPLICATE_INTERVIEW); + } + + @Test + public void execute_invalidInterviewIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredInterviewList().size() + 1); + EditInterviewCommand.EditInterviewDescriptor descriptor = + new EditInterviewDescriptorBuilder().withPosition(VALID_POSITION_ADMIN_NAME).build(); + EditInterviewCommand editInterviewCommand = new EditInterviewCommand(outOfBoundIndex, descriptor); + + assertCommandFailure(editInterviewCommand, model, Messages.MESSAGE_INVALID_INTERVIEW_DISPLAYED_INDEX); + } + + /** + * Edit filtered list where index is larger than size of filtered list, + * but smaller than size of position list + */ + @Test + public void execute_invalidInterviewIndexFilteredList_failure() { + showInterviewAtIndex(model, INDEX_FIRST_INTERVIEW); + Index outOfBoundIndex = INDEX_SECOND_INTERVIEW; + // ensures that outOfBoundIndex is still in bounds of position list + assertTrue(outOfBoundIndex.getZeroBased() < model.getHrManager().getInterviewList().size()); + + EditInterviewCommand editInterviewCommand = new EditInterviewCommand(outOfBoundIndex, + new EditInterviewDescriptorBuilder().withPosition(VALID_POSITION_MANAGER_NAME).build()); + + assertCommandFailure(editInterviewCommand, model, Messages.MESSAGE_INVALID_INTERVIEW_DISPLAYED_INDEX); + } + + @Test + public void equals() { + final EditInterviewCommand standardCommand = + new EditInterviewCommand(INDEX_FIRST_INTERVIEW, DESC_INTERVIEW_ADMIN_ASSISTANT); + + // same values -> returns true + EditInterviewCommand.EditInterviewDescriptor copyDescriptor = + new EditInterviewCommand.EditInterviewDescriptor(DESC_INTERVIEW_ADMIN_ASSISTANT); + EditInterviewCommand commandWithSameValues = new EditInterviewCommand(INDEX_FIRST_INTERVIEW, copyDescriptor); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(standardCommand + .equals(new EditInterviewCommand(INDEX_SECOND_INTERVIEW, DESC_INTERVIEW_ADMIN_ASSISTANT))); + + // different descriptor -> returns false + assertFalse(standardCommand.equals(new EditInterviewCommand(INDEX_FIRST_INTERVIEW, DESC_INTERVIEW_MANAGER))); + } + + @Test + public void execute_positionIsClosed_throwsCommandException() { + ModelStubWithJohn modelStub = new ModelStubWithJohn(); + Interview editedInterview = new InterviewBuilder().build(); + EditInterviewDescriptor descriptor = + new EditInterviewDescriptorBuilder(editedInterview).build(); + descriptor.setPosition(CLOSED_POSITION_CLERK); + EditInterviewCommand editInterviewCommand = new EditInterviewCommand(INDEX_FIRST_INTERVIEW, descriptor); + + assertThrows(CommandException.class, String.format(MESSAGE_POSITION_CLOSED, + CLOSED_POSITION_CLERK.getTitle()), () -> editInterviewCommand.execute(modelStub)); + } + + /** + * A Model stub that contains Typical Person John Doe and other required information. + */ + private class ModelStubWithJohn extends ModelStub { + private final ObservableList<Person> persons = FXCollections.observableArrayList(); + private final ObservableList<Position> positions = FXCollections.observableArrayList(); + private final ObservableList<Interview> interviews = FXCollections.observableArrayList(); + + ModelStubWithJohn() { + persons.add(JOHN); + persons.add(ALICE); + positions.add(CLOSED_POSITION_CLERK); + positions.add(BOOKKEEPER); + interviews.add(new InterviewBuilder().withCandidates(Set.of(JOHN)).withPosition(BOOKKEEPER).build()); + } + + @Override + public ObservableList<Interview> getFilteredInterviewList() { + return interviews; + } + + @Override + public ObservableList<Person> getFilteredPersonList() { + return persons; + } + + @Override + public Person getPerson(Index index) { + return persons.get(index.getZeroBased()); + } + + @Override + public boolean isPositionClosed(Position toCheck) { + return toCheck.getStatus() == Position.PositionStatus.CLOSED; + } + + @Override + public boolean hasPosition(Position position) { + return true; + } + + @Override + public Position getPositionReference(Position position) { + return position; + } + + @Override + public boolean hasInterview(Interview toAdd) { + return interviews.contains(toAdd); + } + + @Override + public void setInterview(Interview target, Interview editedInterview) { + persons.get(0).deleteInterview(target); + persons.get(0).addInterview(editedInterview); + } + + @Override + public void updateFilteredInterviewList(Predicate<Interview> predicate) { + + } + } + +} diff --git a/src/test/java/seedu/address/logic/interview/EditInterviewDescriptorTest.java b/src/test/java/seedu/address/logic/interview/EditInterviewDescriptorTest.java new file mode 100644 index 00000000000..736eb0876e0 --- /dev/null +++ b/src/test/java/seedu/address/logic/interview/EditInterviewDescriptorTest.java @@ -0,0 +1,64 @@ +package seedu.address.logic.interview; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.interview.CommandTestUtil.DESC_INTERVIEW_ADMIN_ASSISTANT; +import static seedu.address.logic.interview.CommandTestUtil.DESC_INTERVIEW_MANAGER; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DATE_OTHER_DATE; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DURATION_TIME_OTHER_DURATION; +import static seedu.address.logic.interview.CommandTestUtil.VALID_POSITION_MANAGER_NAME; +import static seedu.address.logic.interview.CommandTestUtil.VALID_STATUS_COMPLETED; +import static seedu.address.logic.interview.CommandTestUtil.VALID_TIME_OTHER_TIME; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.EditInterviewDescriptorBuilder; + +public class EditInterviewDescriptorTest { + + @Test + public void equals() { + // same values -> returns true + EditInterviewCommand.EditInterviewDescriptor descriptorWithSameValues = + new EditInterviewCommand.EditInterviewDescriptor(DESC_INTERVIEW_ADMIN_ASSISTANT); + assertTrue(DESC_INTERVIEW_ADMIN_ASSISTANT.equals(descriptorWithSameValues)); + + // same object -> returns true + assertTrue(DESC_INTERVIEW_ADMIN_ASSISTANT.equals(DESC_INTERVIEW_ADMIN_ASSISTANT)); + + // null -> returns false + assertFalse(DESC_INTERVIEW_ADMIN_ASSISTANT.equals(null)); + + // different types -> returns false + assertFalse(DESC_INTERVIEW_ADMIN_ASSISTANT.equals(5)); + + // different values -> returns false + assertFalse(DESC_INTERVIEW_ADMIN_ASSISTANT.equals(DESC_INTERVIEW_MANAGER)); + + // different position -> returns false + EditInterviewCommand.EditInterviewDescriptor editedInterview = + new EditInterviewDescriptorBuilder(DESC_INTERVIEW_ADMIN_ASSISTANT) + .withPosition(VALID_POSITION_MANAGER_NAME).build(); + assertFalse(DESC_INTERVIEW_ADMIN_ASSISTANT.equals(editedInterview)); + + // different date -> returns false + editedInterview = new EditInterviewDescriptorBuilder(DESC_INTERVIEW_ADMIN_ASSISTANT) + .withDate(VALID_DATE_OTHER_DATE).build(); + assertFalse(DESC_INTERVIEW_ADMIN_ASSISTANT.equals(editedInterview)); + + // different time -> returns false + editedInterview = new EditInterviewDescriptorBuilder(DESC_INTERVIEW_ADMIN_ASSISTANT) + .withStartTime(VALID_TIME_OTHER_TIME).build(); + assertFalse(DESC_INTERVIEW_ADMIN_ASSISTANT.equals(editedInterview)); + + // different duration -> returns false + editedInterview = new EditInterviewDescriptorBuilder(DESC_INTERVIEW_ADMIN_ASSISTANT) + .withDuration(VALID_DURATION_TIME_OTHER_DURATION).build(); + assertFalse(DESC_INTERVIEW_ADMIN_ASSISTANT.equals(editedInterview)); + + // different status -> return false + editedInterview = new EditInterviewDescriptorBuilder(DESC_INTERVIEW_ADMIN_ASSISTANT) + .withStatus(VALID_STATUS_COMPLETED).build(); + assertFalse(DESC_INTERVIEW_ADMIN_ASSISTANT.equals(editedInterview)); + } +} diff --git a/src/test/java/seedu/address/logic/interview/FindInterviewCommandTest.java b/src/test/java/seedu/address/logic/interview/FindInterviewCommandTest.java new file mode 100644 index 00000000000..8ad28b7021b --- /dev/null +++ b/src/test/java/seedu/address/logic/interview/FindInterviewCommandTest.java @@ -0,0 +1,124 @@ +package seedu.address.logic.interview; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_INTERVIEW_LISTED_OVERVIEW; +import static seedu.address.testutil.TypicalInterviews.ACCOUNTANT_INTERVIEW; +import static seedu.address.testutil.TypicalInterviews.BOOKKEEPER_INTERVIEW_2; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.interview.FindInterviewCommandPredicate; + + + +/** + * Contains integration tests (interaction with the Model) for {@code FindCommand}. + */ +public class FindInterviewCommandTest { + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void equals() { + FindInterviewCommandPredicate firstPredicate = + new FindInterviewCommandPredicate(Collections.singletonList("first"), Arrays.asList(), + Arrays.asList(), Arrays.asList(), Arrays.asList()); + FindInterviewCommandPredicate secondPredicate = + new FindInterviewCommandPredicate(Collections.singletonList("second"), Arrays.asList(), + Arrays.asList(), Arrays.asList(), Arrays.asList()); + + FindInterviewCommand findFirstCommand = new FindInterviewCommand(firstPredicate); + FindInterviewCommand findSecondCommand = new FindInterviewCommand(secondPredicate); + + // same object -> returns true + assertTrue(findFirstCommand.equals(findFirstCommand)); + + // same values -> returns true + FindInterviewCommand findFirstCommandCopy = new FindInterviewCommand(firstPredicate); + assertTrue(findFirstCommand.equals(findFirstCommandCopy)); + + // different types -> returns false + assertFalse(findFirstCommand.equals(1)); + + // null -> returns false + assertFalse(findFirstCommand.equals(null)); + + // different interview -> returns false + assertFalse(findFirstCommand.equals(findSecondCommand)); + } + + @Test + public void execute_multipleKeywords_multipleInterviewsFound() { + String expectedMessage = String.format(MESSAGE_INTERVIEW_LISTED_OVERVIEW, 2); + FindInterviewCommandPredicate predicate = preparePredicate("pending completed"); + Command command = new FindInterviewCommand(predicate); + expectedModel.updateFilteredInterviewList(predicate); + assertEquals(Arrays.asList(ACCOUNTANT_INTERVIEW, BOOKKEEPER_INTERVIEW_2), + expectedModel.getFilteredInterviewList()); + + //have to create a CommandResult manually because assertSuccess uses single parameter constructor + CommandResult expectedCommandResult = new CommandResult(expectedMessage, + CommandResult.CommandType.FIND_I); + + assertCommandSuccess(command, model, expectedCommandResult, expectedModel); + } + + + @Test + public void execute_interviewDoesNotExist_noInterviewFound() { + String expectedMessage = String.format(MESSAGE_INTERVIEW_LISTED_OVERVIEW, 0); + FindInterviewCommandPredicate predicate = preparePredicate("THISSTATSDOESNOTEXIST"); + Command command = new FindInterviewCommand(predicate); + expectedModel.updateFilteredInterviewList(predicate); + assertEquals(Arrays.asList(), expectedModel.getFilteredInterviewList()); + + //have to create a CommandResult manually because assertSuccess uses single parameter constructor + CommandResult expectedCommandResult = new CommandResult(expectedMessage, + CommandResult.CommandType.FIND_I); + + assertCommandSuccess(command, model, expectedCommandResult, expectedModel); + } + + + /** + * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. + */ + private FindInterviewCommandPredicate preparePredicate(String userInput) { + List<String> output = new ArrayList<String>(Arrays.asList(userInput.split("\\s+"))); + output.removeAll(Arrays.asList("", null)); + return new FindInterviewCommandPredicate(Arrays.asList(), Arrays.asList(), + output, Arrays.asList(), Arrays.asList()); + } + + + /** + * Overrides the method in CommandTestUtil for CommandResult created by find_c + */ + private void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, + Model expectedModel) { + try { + CommandResult result = command.execute(actualModel); + assertEquals(expectedCommandResult, result); + assertEquals(expectedModel, actualModel); + + + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } +} diff --git a/src/test/java/seedu/address/logic/interview/ListInterviewCommandTest.java b/src/test/java/seedu/address/logic/interview/ListInterviewCommandTest.java new file mode 100644 index 00000000000..7e5b9e8101f --- /dev/null +++ b/src/test/java/seedu/address/logic/interview/ListInterviewCommandTest.java @@ -0,0 +1,41 @@ +package seedu.address.logic.interview; + +import static seedu.address.logic.interview.CommandTestUtil.assertListCommandSuccess; +import static seedu.address.logic.interview.CommandTestUtil.showInterviewAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_INTERVIEW; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +/** + * Contains integration tests (interaction with the Model) and unit tests for ListInterviewCommand. + */ +public class ListInterviewCommandTest { + + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + expectedModel = new ModelManager(model.getHrManager(), new UserPrefs()); + } + + @Test + public void execute_listIsNotFiltered_showsSameList() { + assertListCommandSuccess(new ListInterviewCommand(), model, ListInterviewCommand.MESSAGE_SUCCESS, + expectedModel); + } + + @Test + public void execute_listIsFiltered_showsEverything() { + showInterviewAtIndex(model, INDEX_FIRST_INTERVIEW); + assertListCommandSuccess(new ListInterviewCommand(), model, ListInterviewCommand.MESSAGE_SUCCESS, + expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/interview/UnassignInterviewCommandTest.java b/src/test/java/seedu/address/logic/interview/UnassignInterviewCommandTest.java new file mode 100644 index 00000000000..0a77266ad3a --- /dev/null +++ b/src/test/java/seedu/address/logic/interview/UnassignInterviewCommandTest.java @@ -0,0 +1,216 @@ +package seedu.address.logic.interview; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static seedu.address.logic.interview.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.interview.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.interview.UnassignInterviewCommand.MESSAGE_ALL_CANDIDATES_REMOVED; +import static seedu.address.logic.interview.UnassignInterviewCommand.MESSAGE_CANDIDATE_DID_NOT_APPLY; +import static seedu.address.logic.interview.UnassignInterviewCommand.MESSAGE_CANDIDATE_INDEX_NOT_VALID; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_INTERVIEW; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.JOHN; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; +import static seedu.address.testutil.TypicalPositions.BOOKKEEPER; +import static seedu.address.testutil.TypicalPositions.CLOSED_POSITION_CLERK; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; +import seedu.address.testutil.InterviewBuilder; +import seedu.address.testutil.ModelStub; + +public class UnassignInterviewCommandTest { + + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Set<Index> indexes = new HashSet<>(); + indexes.add(INDEX_FIRST_PERSON); + UnassignInterviewCommand unassignInterviewCommand = new UnassignInterviewCommand(INDEX_FIRST_INTERVIEW, + indexes); + Interview interviewToUnassign = model.getFilteredInterviewList().get(INDEX_FIRST_INTERVIEW.getZeroBased()); + + + Person alice = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("1. " + alice.getName() + "\n"); + + String expectedMessage = String.format(UnassignInterviewCommand.MESSAGE_SUCCESS, + interviewToUnassign.getDisplayStringWithoutNames(), sb); + + alice.addInterview(interviewToUnassign); + + Set<Person> candidates = new HashSet<>(); + candidates.add(alice); + interviewToUnassign.setCandidates(candidates); + + CommandResult expectedCommandResult = new CommandResult(expectedMessage, CommandResult.CommandType.INTERVIEW); + + ModelManager expectedModel = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + assertCommandSuccess(unassignInterviewCommand, model, expectedCommandResult, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Set<Index> indexes = new HashSet<>(); + indexes.add(INDEX_FIRST_PERSON); + + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredInterviewList().size() + 1); + UnassignInterviewCommand unassignInterviewCommand = new UnassignInterviewCommand(outOfBoundIndex, indexes); + + assertCommandFailure(unassignInterviewCommand, model, Messages.MESSAGE_INVALID_INTERVIEW_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexUnfilteredListTotalWipe_success() { + UnassignInterviewCommand unassignInterviewCommand = new UnassignInterviewCommand(INDEX_FIRST_INTERVIEW, true); + Interview interviewToUnassign = model.getFilteredInterviewList().get(INDEX_FIRST_INTERVIEW.getZeroBased()); + + Person alice = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person benson = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); + Set<Person> candidates = new HashSet<>(); + candidates.add(alice); + candidates.add(benson); + + String expectedMessage = String.format(MESSAGE_ALL_CANDIDATES_REMOVED, + interviewToUnassign.getDisplayStringWithoutNames()); + + alice.addInterview(interviewToUnassign); + benson.addInterview(interviewToUnassign); + interviewToUnassign.setCandidates(candidates); + + CommandResult expectedCommandResult = new CommandResult(expectedMessage, CommandResult.CommandType.INTERVIEW); + + ModelManager expectedModel = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + assertCommandSuccess(unassignInterviewCommand, model, expectedCommandResult, expectedModel); + } + + @Test + public void execute_invalidCandidateIndex_throwsCommandException() { + ModelStubWithObservable model = new ModelStubWithObservable(); + Index validInterviewIndex = Index.fromZeroBased(model.getFilteredInterviewList().size() - 1); + Index invalidCandidateIndex = Index.fromZeroBased(model.getFilteredPersonList().size()); + + UnassignInterviewCommand unassignInterviewCommand = new UnassignInterviewCommand(validInterviewIndex, + Set.of(invalidCandidateIndex)); + + assertThrows(CommandException.class, + String.format(MESSAGE_CANDIDATE_INDEX_NOT_VALID, invalidCandidateIndex.getOneBased()), ( + )-> unassignInterviewCommand.execute(model)); + } + + @Test + public void execute_candidateNotScheduledInTheFirstPlace_throwsCommandException() { + ModelStubWithObservable model = new ModelStubWithObservable(); + Index index = Index.fromZeroBased(0); + + UnassignInterviewCommand unassignInterviewCommand = new UnassignInterviewCommand(index, Set.of(index)); + + assertThrows(CommandException.class, String.format(MESSAGE_CANDIDATE_DID_NOT_APPLY, + index.getOneBased(), model.getPerson(index).getName(), + model.getFilteredInterviewList().get(index.getZeroBased()) + .getDisplayString()), () -> unassignInterviewCommand.execute(model)); + } + + @Test + public void getCandidateIndexes_neverReturnsOptionalEmpty() { + Index validInterviewIndex = Index.fromZeroBased(model.getFilteredInterviewList().size() - 1); + Index validCandidateIndex = Index.fromZeroBased(model.getFilteredPersonList().size() - 1); + + Index invalidInterviewIndex = Index.fromZeroBased(model.getFilteredInterviewList().size()); + + //null is not tested due to use of requireNonNull + + //non-empty candidate index set + UnassignInterviewCommand unassignInterviewCommand = new UnassignInterviewCommand(validInterviewIndex, + Set.of(validCandidateIndex)); + assertFalse(unassignInterviewCommand.getCandidateIndexes().equals(Optional.empty())); + + //empty candidate index set + UnassignInterviewCommand otherUnassignInterviewCommand = new UnassignInterviewCommand(validInterviewIndex, + new HashSet<>()); + assertFalse(otherUnassignInterviewCommand.getCandidateIndexes().equals(Optional.empty())); + + //invalidInterviewIndex and different constructor + UnassignInterviewCommand anotherUnassignInterviewCommand = new UnassignInterviewCommand(invalidInterviewIndex, + false); + assertFalse(anotherUnassignInterviewCommand.getCandidateIndexes().equals(Optional.empty())); + } + + /** + * A Model stub that contains Typical Person John Doe and observable fields. + */ + private class ModelStubWithObservable extends ModelStub { + private final ObservableList<Person> persons = FXCollections.observableArrayList(); + private final ObservableList<Position> positions = FXCollections.observableArrayList(); + private final ObservableList<Interview> interviews = FXCollections.observableArrayList(); + + ModelStubWithObservable() { + persons.add(JOHN); + persons.add(ALICE); + positions.add(CLOSED_POSITION_CLERK); + positions.add(BOOKKEEPER); + interviews.add(new InterviewBuilder().withCandidates(Set.of(JOHN)).withPosition(BOOKKEEPER).build()); + } + + @Override + public ObservableList<Interview> getFilteredInterviewList() { + return interviews; + } + + @Override + public ObservableList<Person> getFilteredPersonList() { + return persons; + } + + @Override + public Person getPerson(Index index) { + return persons.get(index.getZeroBased()); + } + + @Override + public boolean isPositionClosed(Position toCheck) { + return toCheck.getStatus() == Position.PositionStatus.CLOSED; + } + + @Override + public boolean hasInterview(Interview toAdd) { + return interviews.contains(toAdd); + } + + @Override + public void setInterview(Interview target, Interview editedInterview) { + persons.get(0).deleteInterview(target); + persons.get(0).addInterview(editedInterview); + } + + @Override + public void updateFilteredInterviewList(Predicate<Interview> predicate) { + //empty method + } + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddCandidateCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCandidateCommandParserTest.java new file mode 100644 index 00000000000..4cc2a7e9215 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AddCandidateCommandParserTest.java @@ -0,0 +1,171 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.candidate.CommandTestUtil.ADDRESS_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.ADDRESS_DESC_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.EMAIL_DESC_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_ADDRESS_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_EMAIL_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_NAME_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_PHONE_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_POSITION_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_TAG_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.NAME_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.NAME_DESC_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.PHONE_DESC_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.POSITION_ADMIN_ASSISTANT; +import static seedu.address.logic.candidate.CommandTestUtil.POSITION_HR_MANAGER; +import static seedu.address.logic.candidate.CommandTestUtil.PREAMBLE_NON_EMPTY; +import static seedu.address.logic.candidate.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.candidate.CommandTestUtil.TAG_DESC_FRIEND; +import static seedu.address.logic.candidate.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TITLE_ADMIN_ASSISTANT; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TITLE_HR_MANAGER; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalPersons.AMY; +import static seedu.address.testutil.TypicalPersons.BOB; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.candidate.AddCandidateCommand; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.position.Position; +import seedu.address.model.tag.Tag; +import seedu.address.testutil.PersonBuilder; + +public class AddCandidateCommandParserTest { + private AddCandidateCommandParser parser = new AddCandidateCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).withRemark("").build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + POSITION_HR_MANAGER, new AddCandidateCommand(expectedPerson)); + + // multiple names - last name accepted + assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + POSITION_HR_MANAGER, new AddCandidateCommand(expectedPerson)); + + // multiple phones - last phone accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + POSITION_HR_MANAGER, new AddCandidateCommand(expectedPerson)); + + // multiple emails - last email accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + POSITION_HR_MANAGER, new AddCandidateCommand(expectedPerson)); + + // multiple addresses - last address accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + POSITION_HR_MANAGER, new AddCandidateCommand(expectedPerson)); + + // multiple tags - all accepted + Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) + .withRemark("") + .build(); + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + + POSITION_HR_MANAGER, new AddCandidateCommand(expectedPersonMultipleTags)); + + Person expectedPersonMultiplePositions = new PersonBuilder(BOB) + .withPositions(VALID_TITLE_HR_MANAGER, VALID_TITLE_ADMIN_ASSISTANT) + .withRemark("") + .build(); + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + POSITION_ADMIN_ASSISTANT + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + POSITION_HR_MANAGER, + new AddCandidateCommand(expectedPersonMultiplePositions)); + } + + @Test + public void parse_optionalFieldsMissing_success() { + // zero tags + Person expectedPerson = new PersonBuilder(AMY).withTags().withRemark("").build(); + assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + POSITION_HR_MANAGER, + new AddCandidateCommand(expectedPerson)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCandidateCommand.MESSAGE_USAGE); + + // missing name prefix + assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + POSITION_ADMIN_ASSISTANT, + expectedMessage); + + // missing phone prefix + assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + POSITION_ADMIN_ASSISTANT, + expectedMessage); + + // missing email prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB, + expectedMessage); + + // missing address prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB + + POSITION_ADMIN_ASSISTANT, + expectedMessage); + + // missing position prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + VALID_TITLE_ADMIN_ASSISTANT, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB, + expectedMessage); + + } + + @Test + public void parse_invalidValue_failure() { + // invalid name + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + POSITION_HR_MANAGER, Name.MESSAGE_CONSTRAINTS); + + // invalid phone + assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + POSITION_HR_MANAGER, Phone.MESSAGE_CONSTRAINTS); + + // invalid email + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + POSITION_HR_MANAGER, Email.MESSAGE_CONSTRAINTS); + + // invalid address + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + POSITION_HR_MANAGER, Address.MESSAGE_CONSTRAINTS); + + // invalid tag + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + INVALID_TAG_DESC + VALID_TAG_FRIEND + POSITION_HR_MANAGER, Tag.MESSAGE_CONSTRAINTS); + + // invalid position + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + INVALID_POSITION_DESC, Position.MESSAGE_CONSTRAINTS); + + // two invalid values, only first invalid value reported + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + POSITION_HR_MANAGER, Name.MESSAGE_CONSTRAINTS); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND + POSITION_HR_MANAGER, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCandidateCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java deleted file mode 100644 index 5cf487d7ebb..00000000000 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ /dev/null @@ -1,141 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; -import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalPersons.AMY; -import static seedu.address.testutil.TypicalPersons.BOB; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; -import seedu.address.testutil.PersonBuilder; - -public class AddCommandParserTest { - private AddCommandParser parser = new AddCommandParser(); - - @Test - public void parse_allFieldsPresent_success() { - Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build(); - - // whitespace only preamble - assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple names - last name accepted - assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple phones - last phone accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple emails - last email accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple addresses - last address accepted - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - // multiple tags - all accepted - Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); - assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddCommand(expectedPersonMultipleTags)); - } - - @Test - public void parse_optionalFieldsMissing_success() { - // zero tags - Person expectedPerson = new PersonBuilder(AMY).withTags().build(); - assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY, - new AddCommand(expectedPerson)); - } - - @Test - public void parse_compulsoryFieldMissing_failure() { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); - - // missing name prefix - assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing phone prefix - assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing email prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing address prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB, - expectedMessage); - - // all prefixes missing - assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB, - expectedMessage); - } - - @Test - public void parse_invalidValue_failure() { - // invalid name - assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS); - - // invalid phone - assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS); - - // invalid email - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS); - - // invalid address - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS); - - // invalid tag - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS); - - // two invalid values, only first invalid value reported - assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC, - Name.MESSAGE_CONSTRAINTS); - - // non-empty preamble - assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, - String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } -} diff --git a/src/test/java/seedu/address/logic/parser/AddInterviewCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddInterviewCommandParserTest.java new file mode 100644 index 00000000000..5a24cb7a709 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AddInterviewCommandParserTest.java @@ -0,0 +1,131 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.interview.CommandTestUtil.INVALID_DATE_DESC; +import static seedu.address.logic.interview.CommandTestUtil.INVALID_DURATION_TIME; +import static seedu.address.logic.interview.CommandTestUtil.INVALID_STATUS_DESC; +import static seedu.address.logic.interview.CommandTestUtil.INVALID_TIME_DESC; +import static seedu.address.logic.interview.CommandTestUtil.PREAMBLE_NON_EMPTY; +import static seedu.address.logic.interview.CommandTestUtil.VALID_CANDIDATE_DESC_ALICE; +import static seedu.address.logic.interview.CommandTestUtil.VALID_CANDIDATE_DESC_BOB; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DATE; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DATE_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DURATION_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DURATION_TIME; +import static seedu.address.logic.interview.CommandTestUtil.VALID_NAME_ALICE; +import static seedu.address.logic.interview.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.interview.CommandTestUtil.VALID_POSITION_ADMIN; +import static seedu.address.logic.interview.CommandTestUtil.VALID_POSITION_ADMIN_ASST_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_POSITION_ADMIN_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_STATUS_COMPLETED_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_STATUS_PENDING_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_TIME; +import static seedu.address.logic.interview.CommandTestUtil.VALID_TIME_DESC; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.interview.AddInterviewCommand; +import seedu.address.model.interview.Interview; +import seedu.address.testutil.InterviewBuilder; + + + +class AddInterviewCommandParserTest { + private AddInterviewCommandParser parser = new AddInterviewCommandParser(); + + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddInterviewCommand.MESSAGE_USAGE); + + // missing position prefix + assertParseFailure(parser, VALID_POSITION_ADMIN.getTitle().fullTitle + + VALID_CANDIDATE_DESC_ALICE + VALID_CANDIDATE_DESC_BOB + VALID_DATE_DESC + + VALID_TIME_DESC + VALID_DURATION_DESC + VALID_STATUS_COMPLETED_DESC, + expectedMessage); + + // missing date prefix + assertParseFailure(parser, VALID_POSITION_ADMIN_DESC + VALID_CANDIDATE_DESC_ALICE + VALID_CANDIDATE_DESC_BOB + + " " + VALID_DATE + VALID_TIME_DESC + VALID_DURATION_DESC, + expectedMessage); + + // missing time prefix + assertParseFailure(parser, VALID_POSITION_ADMIN_DESC + VALID_CANDIDATE_DESC_ALICE + VALID_CANDIDATE_DESC_BOB + + VALID_DATE_DESC + " " + VALID_TIME + VALID_DURATION_DESC, + expectedMessage); + + // missing duration prefix + assertParseFailure(parser, VALID_POSITION_ADMIN_DESC + VALID_CANDIDATE_DESC_ALICE + VALID_CANDIDATE_DESC_BOB + + VALID_DATE_DESC + VALID_TIME_DESC + " " + VALID_DURATION_TIME, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_POSITION_ADMIN.getTitle() + " " + VALID_NAME_ALICE + " " + + VALID_NAME_BOB + " " + VALID_DATE + " " + VALID_TIME + " " + + VALID_DURATION_TIME, + expectedMessage); + + } + + @Test + public void parse_invalidValue_failure() { + + // invalid date + assertParseFailure(parser, VALID_POSITION_ADMIN_DESC + VALID_CANDIDATE_DESC_ALICE + + VALID_CANDIDATE_DESC_BOB + INVALID_DATE_DESC + + VALID_TIME_DESC + VALID_DURATION_DESC, Interview.MESSAGE_DATE_CONSTRAINTS); + + // invalid time + assertParseFailure(parser, VALID_POSITION_ADMIN_DESC + VALID_CANDIDATE_DESC_ALICE + + VALID_CANDIDATE_DESC_BOB + VALID_DATE_DESC + + INVALID_TIME_DESC + VALID_DURATION_DESC, Interview.MESSAGE_TIME_CONSTRAINTS); + + // invalid duration + assertParseFailure(parser, VALID_POSITION_ADMIN_DESC + VALID_CANDIDATE_DESC_ALICE + + VALID_CANDIDATE_DESC_BOB + VALID_DATE_DESC + + VALID_TIME_DESC + INVALID_DURATION_TIME, Interview.MESSAGE_DURATION_CONSTRAINTS_INVALID_NUMBER); + + // invalid status + assertParseFailure(parser, VALID_POSITION_ADMIN_DESC + VALID_CANDIDATE_DESC_ALICE + + VALID_CANDIDATE_DESC_BOB + VALID_DATE_DESC + + VALID_TIME_DESC + VALID_DURATION_DESC + INVALID_STATUS_DESC, + Interview.InterviewStatus.MESSAGE_CONSTRAINTS); + + // two invalid values, only first invalid value reported + assertParseFailure(parser, VALID_POSITION_ADMIN_DESC + VALID_CANDIDATE_DESC_ALICE + + VALID_CANDIDATE_DESC_BOB + INVALID_DATE_DESC + + VALID_TIME_DESC + VALID_DURATION_DESC + INVALID_STATUS_DESC, Interview.MESSAGE_DATE_CONSTRAINTS); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + VALID_POSITION_ADMIN_DESC + VALID_CANDIDATE_DESC_ALICE + + VALID_CANDIDATE_DESC_BOB + + VALID_DATE_DESC + VALID_TIME_DESC + VALID_DURATION_DESC, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddInterviewCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_allFieldsPresent_success() { + Interview expectedInterview = new InterviewBuilder().withPosition(ADMIN_ASSISTANT) + //at the time when AddInterviewCommand is created, the candidate couldn't have been added before + //execute is called, hence testing with no candidates + .withCandidates(new HashSet<>()) + .withDate(LocalDate.of(2021, 10, 18)) + .withStartTime(LocalTime.of(12, 0)).withDuration(Duration.ofMinutes(180)) + .build(); + String userInput = VALID_POSITION_ADMIN_ASST_DESC + VALID_CANDIDATE_DESC_ALICE + + VALID_DATE_DESC + VALID_TIME_DESC + VALID_DURATION_DESC + VALID_STATUS_PENDING_DESC; + assertParseSuccess(parser, userInput, new AddInterviewCommand(expectedInterview, + Set.of(Index.fromZeroBased(0)))); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/AddPositionCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddPositionCommandParserTest.java new file mode 100644 index 00000000000..7738fa9444b --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AddPositionCommandParserTest.java @@ -0,0 +1,38 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.interview.CommandTestUtil.VALID_POSITION_ADMIN; +import static seedu.address.logic.interview.CommandTestUtil.VALID_POSITION_ADMIN_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.position.AddPositionCommand; +import seedu.address.model.position.Title; + +class AddPositionCommandParserTest { + private AddPositionCommandParser parser = new AddPositionCommandParser(); + + @Test + public void parse_compulsoryFieldMissing_failure() { + //missing title prefix + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPositionCommand.MESSAGE_USAGE); + assertParseFailure(parser, VALID_POSITION_ADMIN_NAME, expectedMessage); + } + + @Test + public void parse_validInput_success() { + AddPositionCommand expectedCommand = new AddPositionCommand(VALID_POSITION_ADMIN); + String userInput = " " + PREFIX_TITLE + VALID_POSITION_ADMIN_NAME; + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_invalidValue_failure() { + // invalid position name + assertParseFailure(parser, " " + PREFIX_TITLE + VALID_POSITION_ADMIN_NAME + "@", + Title.MESSAGE_CONSTRAINTS); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java deleted file mode 100644 index d9659205b57..00000000000 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package seedu.address.logic.parser; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; -import seedu.address.testutil.PersonBuilder; -import seedu.address.testutil.PersonUtil; - -public class AddressBookParserTest { - - private final AddressBookParser parser = new AddressBookParser(); - - @Test - public void parseCommand_add() throws Exception { - Person person = new PersonBuilder().build(); - AddCommand command = (AddCommand) parser.parseCommand(PersonUtil.getAddCommand(person)); - assertEquals(new AddCommand(person), command); - } - - @Test - public void parseCommand_clear() throws Exception { - assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD) instanceof ClearCommand); - assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD + " 3") instanceof ClearCommand); - } - - @Test - public void parseCommand_delete() throws Exception { - DeleteCommand command = (DeleteCommand) parser.parseCommand( - DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command); - } - - @Test - public void parseCommand_edit() throws Exception { - Person person = new PersonBuilder().build(); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build(); - EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " " - + INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor)); - assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command); - } - - @Test - public void parseCommand_exit() throws Exception { - assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand); - assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand); - } - - @Test - public void parseCommand_find() throws Exception { - List<String> keywords = Arrays.asList("foo", "bar", "baz"); - FindCommand command = (FindCommand) parser.parseCommand( - FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); - assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); - } - - @Test - public void parseCommand_help() throws Exception { - assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD) instanceof HelpCommand); - assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD + " 3") instanceof HelpCommand); - } - - @Test - public void parseCommand_list() throws Exception { - assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD) instanceof ListCommand); - assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand); - } - - @Test - public void parseCommand_unrecognisedInput_throwsParseException() { - assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), () - -> parser.parseCommand("")); - } - - @Test - public void parseCommand_unknownCommand_throwsParseException() { - assertThrows(ParseException.class, MESSAGE_UNKNOWN_COMMAND, () -> parser.parseCommand("unknownCommand")); - } -} diff --git a/src/test/java/seedu/address/logic/parser/AssignInterviewCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AssignInterviewCommandParserTest.java new file mode 100644 index 00000000000..396e30d4501 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AssignInterviewCommandParserTest.java @@ -0,0 +1,37 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_INTERVIEW; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.interview.AssignInterviewCommand; + +public class AssignInterviewCommandParserTest { + + private AssignInterviewCommandParser parser = new AssignInterviewCommandParser(); + + @Test + public void parse_validArgs_returnsAssignCommand() { + Set<Index> indexes = new HashSet<>(); + indexes.add(INDEX_FIRST_PERSON); + indexes.add(INDEX_SECOND_PERSON); + + assertParseSuccess(parser, " i=1 c=1 2", new AssignInterviewCommand(INDEX_FIRST_INTERVIEW, indexes)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "i=a c=1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AssignInterviewCommand.MESSAGE_USAGE)); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java b/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java index e4c33515768..fbca82194c1 100644 --- a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java +++ b/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import seedu.address.logic.commands.Command; +import seedu.address.logic.Command; import seedu.address.logic.parser.exceptions.ParseException; /** diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCandidateCommandParserTest.java similarity index 71% rename from src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java rename to src/test/java/seedu/address/logic/parser/DeleteCandidateCommandParserTest.java index 27eaec84450..ce33fd0e88e 100644 --- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/DeleteCandidateCommandParserTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.candidate.DeleteCandidateCommand; /** * As we are only doing white-box testing, our test cases do not cover path variations @@ -16,17 +16,18 @@ * The path variation for those two cases occur inside the ParserUtil, and * therefore should be covered by the ParserUtilTest. */ -public class DeleteCommandParserTest { +public class DeleteCandidateCommandParserTest { - private DeleteCommandParser parser = new DeleteCommandParser(); + private DeleteCandidateCommandParser parser = new DeleteCandidateCommandParser(); @Test public void parse_validArgs_returnsDeleteCommand() { - assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON)); + assertParseSuccess(parser, "1", new DeleteCandidateCommand(INDEX_FIRST_PERSON)); } @Test public void parse_invalidArgs_throwsParseException() { - assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ParserUtil.MESSAGE_INVALID_INDEX) + DeleteCandidateCommand.MESSAGE_USAGE); } } diff --git a/src/test/java/seedu/address/logic/parser/DeleteInterviewCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteInterviewCommandParserTest.java new file mode 100644 index 00000000000..d2a0438018e --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DeleteInterviewCommandParserTest.java @@ -0,0 +1,26 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.interview.DeleteInterviewCommand; + +public class DeleteInterviewCommandParserTest { + + private DeleteInterviewCommandParser parser = new DeleteInterviewCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteCommand() { + assertParseSuccess(parser, "1", new DeleteInterviewCommand(INDEX_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ParserUtil.MESSAGE_INVALID_INDEX) + DeleteInterviewCommand.MESSAGE_USAGE); + } +} diff --git a/src/test/java/seedu/address/logic/parser/DeletePositionCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeletePositionCommandParserTest.java new file mode 100644 index 00000000000..a84e6a84707 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/DeletePositionCommandParserTest.java @@ -0,0 +1,26 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_POSITION; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.position.DeletePositionCommand; + +class DeletePositionCommandParserTest { + + private DeletePositionCommandParser parser = new DeletePositionCommandParser(); + + @Test + public void parse_validArgs_returnsDeletePositionCommand() { + assertParseSuccess(parser, "1", new DeletePositionCommand(INDEX_FIRST_POSITION)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ParserUtil.MESSAGE_INVALID_INDEX) + DeletePositionCommand.MESSAGE_USAGE); + } +} diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCandidateCommandParserTest.java similarity index 58% rename from src/test/java/seedu/address/logic/parser/EditCommandParserTest.java rename to src/test/java/seedu/address/logic/parser/EditCandidateCommandParserTest.java index 2ff31522486..e38ee1fc98c 100644 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/EditCandidateCommandParserTest.java @@ -1,29 +1,37 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.candidate.CommandTestUtil.ADDRESS_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.ADDRESS_DESC_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.EMAIL_DESC_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_ADDRESS_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_EMAIL_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_NAME_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_PHONE_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_POSITION_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.INVALID_TAG_DESC; +import static seedu.address.logic.candidate.CommandTestUtil.NAME_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.PHONE_DESC_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.POSITION_ADMIN_ASSISTANT; +import static seedu.address.logic.candidate.CommandTestUtil.POSITION_HR_MANAGER; +import static seedu.address.logic.candidate.CommandTestUtil.TAG_DESC_FRIEND; +import static seedu.address.logic.candidate.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_ADDRESS_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_STATUS_APPLIED; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TITLE_ADMIN_ASSISTANT; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TITLE_HR_MANAGER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; @@ -34,34 +42,45 @@ import org.junit.jupiter.api.Test; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.candidate.EditCandidateCommand; +import seedu.address.logic.candidate.EditCandidateCommand.EditPersonDescriptor; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Status; +import seedu.address.model.position.Position; import seedu.address.model.tag.Tag; import seedu.address.testutil.EditPersonDescriptorBuilder; -public class EditCommandParserTest { +public class EditCandidateCommandParserTest { private static final String TAG_EMPTY = " " + PREFIX_TAG; private static final String MESSAGE_INVALID_FORMAT = - String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ParserUtil.MESSAGE_INVALID_INDEX) + EditCandidateCommand.MESSAGE_USAGE; - private EditCommandParser parser = new EditCommandParser(); + private static final String MESSAGE_INVALID_FORMAT_EMPTY_INDEX = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCandidateCommand.MESSAGE_USAGE); + + + private EditCandidateCommandParser parser = new EditCandidateCommandParser(); @Test public void parse_missingParts_failure() { - // no index specified + // no index specified, followed by field name=value + assertParseFailure(parser, " " + PREFIX_NAME + VALID_NAME_AMY, MESSAGE_INVALID_FORMAT_EMPTY_INDEX); + + + // no index specified, immediately followed by field value assertParseFailure(parser, VALID_NAME_AMY, MESSAGE_INVALID_FORMAT); // no field specified - assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED); + assertParseFailure(parser, "1", EditCandidateCommand.MESSAGE_NOT_EDITED); // no index and no field specified - assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); + assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT_EMPTY_INDEX); } @Test @@ -86,6 +105,7 @@ public void parse_invalidValue_failure() { assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email assertParseFailure(parser, "1" + INVALID_ADDRESS_DESC, Address.MESSAGE_CONSTRAINTS); // invalid address assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag + assertParseFailure(parser, "1" + INVALID_POSITION_DESC, Position.MESSAGE_CONSTRAINTS); // invalid tag // invalid phone followed by valid email assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS); @@ -109,12 +129,12 @@ public void parse_invalidValue_failure() { public void parse_allFieldsSpecified_success() { Index targetIndex = INDEX_SECOND_PERSON; String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND - + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND; + + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND + POSITION_HR_MANAGER; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) - .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).withPositions(VALID_TITLE_HR_MANAGER).build(); + EditCandidateCommand expectedCommand = new EditCandidateCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); } @@ -126,7 +146,7 @@ public void parse_someFieldsSpecified_success() { EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) .withEmail(VALID_EMAIL_AMY).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + EditCandidateCommand expectedCommand = new EditCandidateCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); } @@ -137,32 +157,45 @@ public void parse_oneFieldSpecified_success() { Index targetIndex = INDEX_THIRD_PERSON; String userInput = targetIndex.getOneBased() + NAME_DESC_AMY; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + EditCandidateCommand expectedCommand = new EditCandidateCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); // phone userInput = targetIndex.getOneBased() + PHONE_DESC_AMY; descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); + expectedCommand = new EditCandidateCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); // email userInput = targetIndex.getOneBased() + EMAIL_DESC_AMY; descriptor = new EditPersonDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); + expectedCommand = new EditCandidateCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); // address userInput = targetIndex.getOneBased() + ADDRESS_DESC_AMY; descriptor = new EditPersonDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); + expectedCommand = new EditCandidateCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); // tags userInput = targetIndex.getOneBased() + TAG_DESC_FRIEND; descriptor = new EditPersonDescriptorBuilder().withTags(VALID_TAG_FRIEND).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); + expectedCommand = new EditCandidateCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); + + // positions + userInput = targetIndex.getOneBased() + POSITION_HR_MANAGER; + descriptor = new EditPersonDescriptorBuilder().withPositions(VALID_TITLE_HR_MANAGER).build(); + expectedCommand = new EditCandidateCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // status + userInput = targetIndex.getOneBased() + " " + PREFIX_STATUS + VALID_STATUS_APPLIED; + descriptor = new EditPersonDescriptorBuilder().withStatus(Status.APPLIED).build(); + expectedCommand = new EditCandidateCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } @Test @@ -170,12 +203,13 @@ public void parse_multipleRepeatedFields_acceptsLast() { Index targetIndex = INDEX_FIRST_PERSON; String userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND - + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND; + + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND + POSITION_HR_MANAGER + + POSITION_ADMIN_ASSISTANT; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + .withPositions(VALID_TITLE_HR_MANAGER, VALID_TITLE_ADMIN_ASSISTANT).build(); + EditCandidateCommand expectedCommand = new EditCandidateCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); } @@ -186,7 +220,7 @@ public void parse_invalidValueFollowedByValidValue_success() { Index targetIndex = INDEX_FIRST_PERSON; String userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + PHONE_DESC_BOB; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + EditCandidateCommand expectedCommand = new EditCandidateCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); // other valid values specified @@ -194,7 +228,7 @@ public void parse_invalidValueFollowedByValidValue_success() { + PHONE_DESC_BOB; descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) .withAddress(VALID_ADDRESS_BOB).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); + expectedCommand = new EditCandidateCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); } @@ -204,7 +238,7 @@ public void parse_resetTags_success() { String userInput = targetIndex.getOneBased() + TAG_EMPTY; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + EditCandidateCommand expectedCommand = new EditCandidateCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); } diff --git a/src/test/java/seedu/address/logic/parser/EditInterviewCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditInterviewCommandParserTest.java new file mode 100644 index 00000000000..dfbe499c476 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/EditInterviewCommandParserTest.java @@ -0,0 +1,208 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.interview.CommandTestUtil.INVALID_DATE_DESC; +import static seedu.address.logic.interview.CommandTestUtil.INVALID_DURATION_TIME; +import static seedu.address.logic.interview.CommandTestUtil.INVALID_POSITION_DESC; +import static seedu.address.logic.interview.CommandTestUtil.INVALID_STATUS_DESC; +import static seedu.address.logic.interview.CommandTestUtil.INVALID_TIME_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DATE; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DATE_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DATE_OTHER_DATE; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DATE_OTHER_DATE_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DURATION_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DURATION_TIME; +import static seedu.address.logic.interview.CommandTestUtil.VALID_POSITION_ADMIN_ASSISTANT; +import static seedu.address.logic.interview.CommandTestUtil.VALID_POSITION_ADMIN_ASST_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_STATUS_COMPLETED; +import static seedu.address.logic.interview.CommandTestUtil.VALID_STATUS_COMPLETED_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_STATUS_PENDING; +import static seedu.address.logic.interview.CommandTestUtil.VALID_STATUS_PENDING_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_TIME; +import static seedu.address.logic.interview.CommandTestUtil.VALID_TIME_DESC; +import static seedu.address.logic.interview.CommandTestUtil.VALID_TIME_OTHER_TIME; +import static seedu.address.logic.interview.CommandTestUtil.VALID_TIME_OTHER_TIME_DESC; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_INTERVIEW; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_POSITION; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_INTERVIEW; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.interview.EditInterviewCommand; +import seedu.address.model.interview.Interview; +import seedu.address.model.position.Position; +import seedu.address.testutil.EditInterviewDescriptorBuilder; + +public class EditInterviewCommandParserTest { + + private static final String MESSAGE_INVALID_FORMAT = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ParserUtil.MESSAGE_INVALID_INDEX) + EditInterviewCommand.MESSAGE_USAGE; + + private static final String MESSAGE_INVALID_FORMAT_EMPTY_INDEX = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditInterviewCommand.MESSAGE_USAGE); + + private EditInterviewCommandParser parser = new EditInterviewCommandParser(); + + @Test + public void parse_missingParts_failure() { + // no index specified, followed by field name=value + assertParseFailure(parser, VALID_DATE_DESC, MESSAGE_INVALID_FORMAT_EMPTY_INDEX); + + // no index specified, immediately followed by field value + assertParseFailure(parser, VALID_DATE, MESSAGE_INVALID_FORMAT); + + // no field specified + assertParseFailure(parser, "1", EditInterviewCommand.MESSAGE_NOT_EDITED); + + // no index and no field specified + assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT_EMPTY_INDEX); + } + + @Test + public void parse_invalidPreamble_failure() { + // negative index + assertParseFailure(parser, "-5" + VALID_DATE_DESC, MESSAGE_INVALID_FORMAT); + + // zero index + assertParseFailure(parser, "0" + VALID_DATE_DESC, MESSAGE_INVALID_FORMAT); + + // invalid arguments being parsed as preamble + assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); + + // invalid prefix being parsed as preamble + assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidValue_failure() { + //invalid position + assertParseFailure(parser, "1" + INVALID_POSITION_DESC, Position.MESSAGE_CONSTRAINTS); + + //invalid date + assertParseFailure(parser, "1" + INVALID_DATE_DESC, Interview.MESSAGE_DATE_CONSTRAINTS); + + //invalid time + assertParseFailure(parser, "1" + INVALID_TIME_DESC, Interview.MESSAGE_TIME_CONSTRAINTS); + + //invalid duration + assertParseFailure(parser, "1" + INVALID_DURATION_TIME, Interview.MESSAGE_DURATION_CONSTRAINTS_INVALID_NUMBER); + + //invalid status + assertParseFailure(parser, "1" + INVALID_STATUS_DESC, Interview.InterviewStatus.MESSAGE_CONSTRAINTS); + + // invalid date followed by valid time + assertParseFailure(parser, + "1" + INVALID_DATE_DESC + VALID_TIME_DESC, Interview.MESSAGE_DATE_CONSTRAINTS); + + // valid date followed by invalid time. + assertParseFailure(parser, + "1" + VALID_DATE_DESC + INVALID_TIME_DESC, Interview.MESSAGE_TIME_CONSTRAINTS); + + // multiple invalid values, but only the first invalid value is captured + assertParseFailure(parser, "1" + INVALID_DATE_DESC + INVALID_TIME_DESC, + Interview.MESSAGE_DATE_CONSTRAINTS); + } + + @Test + public void parse_allFieldsSpecified_success() { + Index targetIndex = INDEX_SECOND_INTERVIEW; + String userInput = targetIndex.getOneBased() + VALID_POSITION_ADMIN_ASST_DESC + + VALID_DATE_DESC + VALID_TIME_DESC + VALID_DURATION_DESC + VALID_STATUS_PENDING_DESC; + + EditInterviewCommand.EditInterviewDescriptor descriptor = + new EditInterviewDescriptorBuilder() + .withPosition(VALID_POSITION_ADMIN_ASSISTANT) + .withDate(VALID_DATE) + .withStartTime(VALID_TIME) + .withDuration(VALID_DURATION_TIME) + .withStatus(VALID_STATUS_PENDING).build(); + EditInterviewCommand expectedCommand = new EditInterviewCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_someFieldsSpecified_success() { + Index targetIndex = INDEX_FIRST_INTERVIEW; + String userInput = targetIndex.getOneBased() + VALID_TIME_DESC + VALID_DURATION_DESC; + + EditInterviewCommand.EditInterviewDescriptor descriptor = + new EditInterviewDescriptorBuilder() + .withStartTime(VALID_TIME).withDuration(VALID_DURATION_TIME).build(); + EditInterviewCommand expectedCommand = new EditInterviewCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_oneFieldSpecified_success() { + // position + Index targetIndex = INDEX_SECOND_INTERVIEW; + String userInput = targetIndex.getOneBased() + VALID_POSITION_ADMIN_ASST_DESC; + EditInterviewCommand.EditInterviewDescriptor descriptor = + new EditInterviewDescriptorBuilder().withPosition(VALID_POSITION_ADMIN_ASSISTANT).build(); + EditInterviewCommand expectedCommand = new EditInterviewCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // date + userInput = targetIndex.getOneBased() + VALID_DATE_DESC; + descriptor = new EditInterviewDescriptorBuilder().withDate(VALID_DATE).build(); + expectedCommand = new EditInterviewCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // time + userInput = targetIndex.getOneBased() + VALID_TIME_DESC; + descriptor = new EditInterviewDescriptorBuilder().withStartTime(VALID_TIME).build(); + expectedCommand = new EditInterviewCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // duration + userInput = targetIndex.getOneBased() + VALID_DURATION_DESC; + descriptor = new EditInterviewDescriptorBuilder().withDuration(VALID_DURATION_TIME).build(); + expectedCommand = new EditInterviewCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // status + userInput = targetIndex.getOneBased() + VALID_STATUS_COMPLETED_DESC; + descriptor = new EditInterviewDescriptorBuilder().withStatus(VALID_STATUS_COMPLETED).build(); + expectedCommand = new EditInterviewCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_multipleRepeatedFields_acceptsLast() { + Index targetIndex = INDEX_FIRST_POSITION; + String userInput = targetIndex.getOneBased() + VALID_DATE_DESC + VALID_DATE_OTHER_DATE_DESC + + VALID_TIME_DESC + VALID_TIME_OTHER_TIME_DESC; + + EditInterviewCommand.EditInterviewDescriptor descriptor = + new EditInterviewDescriptorBuilder().withDate(VALID_DATE_OTHER_DATE) + .withStartTime(VALID_TIME_OTHER_TIME).build(); + EditInterviewCommand expectedCommand = new EditInterviewCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_invalidValueFollowedByValidValue_success() { + // no other valid values specified + Index targetIndex = INDEX_FIRST_INTERVIEW; + String userInput = targetIndex.getOneBased() + INVALID_DATE_DESC + VALID_DATE_DESC; + EditInterviewCommand.EditInterviewDescriptor descriptor = + new EditInterviewDescriptorBuilder().withDate(VALID_DATE).build(); + EditInterviewCommand expectedCommand = new EditInterviewCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // other valid values specified + userInput = targetIndex.getOneBased() + VALID_DATE_DESC + INVALID_TIME_DESC + VALID_TIME_DESC; + descriptor = new EditInterviewDescriptorBuilder() + .withDate(VALID_DATE).withStartTime(VALID_TIME).build(); + expectedCommand = new EditInterviewCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/EditPositionCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditPositionCommandParserTest.java new file mode 100644 index 00000000000..84097eebd39 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/EditPositionCommandParserTest.java @@ -0,0 +1,160 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.logic.position.CommandTestUtil.INVALID_POSITION_STATUS; +import static seedu.address.logic.position.CommandTestUtil.INVALID_POSITION_TITLE; +import static seedu.address.logic.position.CommandTestUtil.POSITION_ADMIN_ASSISTANT; +import static seedu.address.logic.position.CommandTestUtil.POSITION_BOOKKEEPER; +import static seedu.address.logic.position.CommandTestUtil.POSITION_HR_MANAGER; +import static seedu.address.logic.position.CommandTestUtil.STATUS_OPEN; +import static seedu.address.logic.position.CommandTestUtil.VALID_STATUS_OPEN; +import static seedu.address.logic.position.CommandTestUtil.VALID_TITLE_ADMIN_ASSISTANT; +import static seedu.address.logic.position.CommandTestUtil.VALID_TITLE_BOOKKEEPER; +import static seedu.address.logic.position.CommandTestUtil.VALID_TITLE_HR_MANAGER; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_POSITION; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_POSITION; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_POSITION; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.position.EditPositionCommand; +import seedu.address.model.position.Position.PositionStatus; +import seedu.address.model.position.Title; +import seedu.address.testutil.EditPositionDescriptorBuilder; + +public class EditPositionCommandParserTest { + + private static final String MESSAGE_INVALID_FORMAT = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ParserUtil.MESSAGE_INVALID_INDEX) + EditPositionCommand.MESSAGE_USAGE; + + private static final String MESSAGE_INVALID_FORMAT_EMPTY_INDEX = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditPositionCommand.MESSAGE_USAGE); + + private EditPositionCommandParser parser = new EditPositionCommandParser(); + + @Test + public void parse_missingParts_failure() { + // no index specified, followed by field name=value + assertParseFailure(parser, + " " + PREFIX_TITLE + VALID_TITLE_ADMIN_ASSISTANT, MESSAGE_INVALID_FORMAT_EMPTY_INDEX); + + // no index specified, immediately followed by field value + assertParseFailure(parser, VALID_TITLE_ADMIN_ASSISTANT, MESSAGE_INVALID_FORMAT); + + // no field specified + assertParseFailure(parser, "1", EditPositionCommand.MESSAGE_NOT_EDITED); + + // no index and no field specified + assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT_EMPTY_INDEX); + } + + @Test + public void parse_invalidPreamble_failure() { + // negative index + assertParseFailure(parser, "-5" + POSITION_ADMIN_ASSISTANT, MESSAGE_INVALID_FORMAT); + + // zero index + assertParseFailure(parser, "0" + POSITION_ADMIN_ASSISTANT, MESSAGE_INVALID_FORMAT); + + // invalid arguments being parsed as preamble + assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); + + // invalid prefix being parsed as preamble + assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidValue_failure() { + assertParseFailure(parser, "1" + INVALID_POSITION_TITLE, Title.MESSAGE_CONSTRAINTS); // invalid title + + // invalid position title followed by valid status + assertParseFailure(parser, "1" + INVALID_POSITION_TITLE + STATUS_OPEN, Title.MESSAGE_CONSTRAINTS); + + // valid position title followed by invalid status. + assertParseFailure(parser, + "1" + POSITION_BOOKKEEPER + INVALID_POSITION_STATUS, PositionStatus.MESSAGE_CONSTRAINTS); + + // multiple invalid values, but only the first invalid value is captured + assertParseFailure(parser, "1" + INVALID_POSITION_TITLE + INVALID_POSITION_STATUS, + Title.MESSAGE_CONSTRAINTS); + } + + @Test + public void parse_allFieldsSpecified_success() { + Index targetIndex = INDEX_SECOND_POSITION; + String userInput = targetIndex.getOneBased() + POSITION_BOOKKEEPER + STATUS_OPEN; + + EditPositionCommand.EditPositionDescriptor descriptor = + new EditPositionDescriptorBuilder().withTitle(VALID_TITLE_BOOKKEEPER) + .withPositionStatus(VALID_STATUS_OPEN).build(); + EditPositionCommand expectedCommand = new EditPositionCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_someFieldsSpecified_success() { + Index targetIndex = INDEX_FIRST_POSITION; + String userInput = targetIndex.getOneBased() + POSITION_BOOKKEEPER; + + EditPositionCommand.EditPositionDescriptor descriptor = + new EditPositionDescriptorBuilder().withTitle(VALID_TITLE_BOOKKEEPER).build(); + EditPositionCommand expectedCommand = new EditPositionCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_oneFieldSpecified_success() { + // title + Index targetIndex = INDEX_THIRD_POSITION; + String userInput = targetIndex.getOneBased() + POSITION_ADMIN_ASSISTANT; + EditPositionCommand.EditPositionDescriptor descriptor = + new EditPositionDescriptorBuilder().withTitle(VALID_TITLE_ADMIN_ASSISTANT).build(); + EditPositionCommand expectedCommand = new EditPositionCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // status + userInput = targetIndex.getOneBased() + STATUS_OPEN; + descriptor = new EditPositionDescriptorBuilder().withPositionStatus(VALID_STATUS_OPEN).build(); + expectedCommand = new EditPositionCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_multipleRepeatedFields_acceptsLast() { + Index targetIndex = INDEX_FIRST_POSITION; + String userInput = targetIndex.getOneBased() + POSITION_ADMIN_ASSISTANT + STATUS_OPEN + + POSITION_ADMIN_ASSISTANT + STATUS_OPEN; + + EditPositionCommand.EditPositionDescriptor descriptor = + new EditPositionDescriptorBuilder().withTitle(VALID_TITLE_ADMIN_ASSISTANT) + .withPositionStatus(VALID_STATUS_OPEN).build(); + EditPositionCommand expectedCommand = new EditPositionCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_invalidValueFollowedByValidValue_success() { + // no other valid values specified + Index targetIndex = INDEX_FIRST_POSITION; + String userInput = targetIndex.getOneBased() + INVALID_POSITION_TITLE + POSITION_HR_MANAGER; + EditPositionCommand.EditPositionDescriptor descriptor = + new EditPositionDescriptorBuilder().withTitle(VALID_TITLE_HR_MANAGER).build(); + EditPositionCommand expectedCommand = new EditPositionCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // other valid values specified + userInput = targetIndex.getOneBased() + POSITION_BOOKKEEPER + INVALID_POSITION_STATUS + STATUS_OPEN; + descriptor = new EditPositionDescriptorBuilder() + .withTitle(VALID_TITLE_BOOKKEEPER).withPositionStatus(VALID_STATUS_OPEN).build(); + expectedCommand = new EditPositionCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } +} diff --git a/src/test/java/seedu/address/logic/parser/FindCandidateCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCandidateCommandParserTest.java new file mode 100644 index 00000000000..5ad0ee0d521 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/FindCandidateCommandParserTest.java @@ -0,0 +1,105 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.TypicalPersons.CARL; +import static seedu.address.testutil.TypicalPersons.ELLE; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.candidate.FindCandidateCommand; +import seedu.address.model.person.FindCandidateCommandPredicate; + +public class FindCandidateCommandParserTest { + + private FindCandidateCommandParser parser = new FindCandidateCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindCandidateCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindCommand() { + // no leading and trailing whitespaces + FindCandidateCommand expectedFindCandidateCommand = + new FindCandidateCommand(new FindCandidateCommandPredicate(Arrays.asList("Alice", "Bob"))); + assertParseSuccess(parser, " " + PREFIX_NAME + "Alice Bob", + expectedFindCandidateCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " " + PREFIX_NAME + " \n Alice \n \t Bob \t", + expectedFindCandidateCommand); + + + //find by phone + expectedFindCandidateCommand = + new FindCandidateCommand(new FindCandidateCommandPredicate(new ArrayList<>(), + Arrays.asList(ALICE.getPhone().value), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), new ArrayList<>())); + + assertParseSuccess(parser, " " + PREFIX_PHONE + ALICE.getPhone().value, + expectedFindCandidateCommand); + + //find by email + expectedFindCandidateCommand = + new FindCandidateCommand(new FindCandidateCommandPredicate(new ArrayList<>(), + new ArrayList<>(), Arrays.asList(BENSON.getEmail().value), new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), new ArrayList<>())); + + assertParseSuccess(parser, " " + PREFIX_EMAIL + BENSON.getEmail().value, + expectedFindCandidateCommand); + + //find by address + expectedFindCandidateCommand = + new FindCandidateCommand(new FindCandidateCommandPredicate(new ArrayList<>(), + new ArrayList<>(), new ArrayList<>(), Arrays.asList(CARL.getAddress().value.split(" ")), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); + + assertParseSuccess(parser, " " + PREFIX_ADDRESS + CARL.getAddress().value, + expectedFindCandidateCommand); + + //find by tag + expectedFindCandidateCommand = + new FindCandidateCommand(new FindCandidateCommandPredicate(new ArrayList<>(), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + Arrays.asList(BENSON.getTagsString().split(" ")), + new ArrayList<>(), new ArrayList<>())); + + assertParseSuccess(parser, " " + PREFIX_TAG + BENSON.getTagsString(), + expectedFindCandidateCommand); + + + //find by status + expectedFindCandidateCommand = + new FindCandidateCommand(new FindCandidateCommandPredicate(new ArrayList<>(), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + Arrays.asList(ELLE.getStatus().toString()), new ArrayList<>())); + + assertParseSuccess(parser, " " + PREFIX_STATUS + ELLE.getStatus(), + expectedFindCandidateCommand); + + //find by position + expectedFindCandidateCommand = + new FindCandidateCommand(new FindCandidateCommandPredicate(new ArrayList<>(), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), Arrays.asList(BENSON.getPositionsString().split(" ")))); + + assertParseSuccess(parser, " " + PREFIX_POSITION + BENSON.getPositionsString(), + expectedFindCandidateCommand); + } +} diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java deleted file mode 100644 index 70f4f0e79c4..00000000000 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; - -import java.util.Arrays; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -public class FindCommandParserTest { - - private FindCommandParser parser = new FindCommandParser(); - - @Test - public void parse_emptyArg_throwsParseException() { - assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - @Test - public void parse_validArgs_returnsFindCommand() { - // no leading and trailing whitespaces - FindCommand expectedFindCommand = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"))); - assertParseSuccess(parser, "Alice Bob", expectedFindCommand); - - // multiple whitespaces between keywords - assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand); - } - -} diff --git a/src/test/java/seedu/address/logic/parser/FindInterviewCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindInterviewCommandParserTest.java new file mode 100644 index 00000000000..f41c2005c61 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/FindInterviewCommandParserTest.java @@ -0,0 +1,88 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.interview.FindInterviewCommand; +import seedu.address.model.interview.FindInterviewCommandPredicate; +import seedu.address.model.interview.Interview; + +public class FindInterviewCommandParserTest { + + private FindInterviewCommandParser parser = new FindInterviewCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindInterviewCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindCommand() { + // no leading and trailing whitespaces + FindInterviewCommand expectedFindInterviewCommand; + expectedFindInterviewCommand = new FindInterviewCommand(new FindInterviewCommandPredicate( + Arrays.asList("Alex", "Mark"), + Arrays.asList("2021", "09"), + Arrays.asList("complete", "pending"), + Arrays.asList("accountant"), + Arrays.asList(LocalTime.of(12, 00)) + )); + assertParseSuccess(parser, " " + + PREFIX_CANDIDATE_INDEX + "Alex Mark " + + PREFIX_DATE + "2021 09 " + + PREFIX_INTERVIEW_STATUS + "complete pending " + + PREFIX_POSITION + "accountant " + + PREFIX_TIME + "1200", + expectedFindInterviewCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " " + + PREFIX_CANDIDATE_INDEX + "\n Alex \n \t Mark " + + PREFIX_DATE + "2021 09 " + + PREFIX_INTERVIEW_STATUS + " \n \t complete pending " + + PREFIX_POSITION + " \n \t accountant \n \t " + + PREFIX_TIME + " \n \t 1200 \n \t ", + expectedFindInterviewCommand); + + + //find by status pending + expectedFindInterviewCommand = + new FindInterviewCommand( + new FindInterviewCommandPredicate( + Arrays.asList(), + Arrays.asList(), + Arrays.asList(Interview.InterviewStatus.PENDING.toString()), + Arrays.asList(), + Arrays.asList() + ) + ); + assertParseSuccess(parser, " " + PREFIX_INTERVIEW_STATUS + Interview.InterviewStatus.PENDING.toString(), + expectedFindInterviewCommand); + + //find by status Completed + expectedFindInterviewCommand = + new FindInterviewCommand(new FindInterviewCommandPredicate( + Arrays.asList(), + Arrays.asList(), + new ArrayList<String>(Arrays.asList(Interview.InterviewStatus.COMPLETED.toString())), + Arrays.asList(), + Arrays.asList() + )); + assertParseSuccess(parser, " " + PREFIX_INTERVIEW_STATUS + Interview.InterviewStatus.COMPLETED, + expectedFindInterviewCommand); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/FindPositionCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindPositionCommandParserTest.java new file mode 100644 index 00000000000..9d8fe8272c0 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/FindPositionCommandParserTest.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.position.FindPositionCommand; +import seedu.address.model.position.FindPositionCommandPredicate; +import seedu.address.model.position.Position; + +public class FindPositionCommandParserTest { + + private FindPositionCommandParser parser = new FindPositionCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindPositionCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindCommand() { + // no leading and trailing whitespaces + FindPositionCommand expectedFindPositionCommand = + new FindPositionCommand(new FindPositionCommandPredicate(Arrays.asList("Assistant", "HR"))); + assertParseSuccess(parser, " " + PREFIX_TITLE + "Assistant HR", + expectedFindPositionCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " " + PREFIX_TITLE + " \n Assistant \n \t HR \t", + expectedFindPositionCommand); + + //find by status closed + expectedFindPositionCommand = + new FindPositionCommand(new FindPositionCommandPredicate(new ArrayList<>(), + Arrays.asList(Position.PositionStatus.CLOSED.toString()))); + assertParseSuccess(parser, " " + PREFIX_POSITION_STATUS + Position.PositionStatus.CLOSED, + expectedFindPositionCommand); + + //find by status open + expectedFindPositionCommand = + new FindPositionCommand(new FindPositionCommandPredicate(new ArrayList<>(), + Arrays.asList(Position.PositionStatus.OPEN.toString()))); + assertParseSuccess(parser, " " + PREFIX_POSITION_STATUS + Position.PositionStatus.OPEN, + expectedFindPositionCommand); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/HrManagerParserTest.java b/src/test/java/seedu/address/logic/parser/HrManagerParserTest.java new file mode 100644 index 00000000000..0bd0148e412 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/HrManagerParserTest.java @@ -0,0 +1,259 @@ +package seedu.address.logic.parser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_REMARK_AMY; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DATE; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DURATION_TIME; +import static seedu.address.logic.interview.CommandTestUtil.VALID_POSITION_ADMIN_ASSISTANT; +import static seedu.address.logic.interview.CommandTestUtil.VALID_STATUS_PENDING; +import static seedu.address.logic.interview.CommandTestUtil.VALID_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_INTERVIEW; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_POSITION; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.Command; +import seedu.address.logic.candidate.AddCandidateCommand; +import seedu.address.logic.candidate.DeleteCandidateCommand; +import seedu.address.logic.candidate.EditCandidateCommand; +import seedu.address.logic.candidate.EditCandidateCommand.EditPersonDescriptor; +import seedu.address.logic.candidate.FindCandidateCommand; +import seedu.address.logic.candidate.ListCandidateCommand; +import seedu.address.logic.candidate.RemarkCandidateCommand; +import seedu.address.logic.general.ClearCommand; +import seedu.address.logic.general.ExitCommand; +import seedu.address.logic.general.HelpCommand; +import seedu.address.logic.interview.AddInterviewCommand; +import seedu.address.logic.interview.DeleteInterviewCommand; +import seedu.address.logic.interview.EditInterviewCommand; +import seedu.address.logic.interview.EditInterviewCommand.EditInterviewDescriptor; +import seedu.address.logic.interview.ListInterviewCommand; +import seedu.address.logic.interview.UnassignInterviewCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.position.AddPositionCommand; +import seedu.address.logic.position.DeletePositionCommand; +import seedu.address.logic.position.EditPositionCommand; +import seedu.address.logic.position.EditPositionCommand.EditPositionDescriptor; +import seedu.address.logic.position.FindPositionCommand; +import seedu.address.logic.position.ListPositionCommand; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.FindCandidateCommandPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.position.FindPositionCommandPredicate; +import seedu.address.model.position.Position; +import seedu.address.testutil.EditInterviewDescriptorBuilder; +import seedu.address.testutil.EditPersonDescriptorBuilder; +import seedu.address.testutil.EditPositionDescriptorBuilder; +import seedu.address.testutil.InterviewBuilder; +import seedu.address.testutil.InterviewUtil; +import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.PersonUtil; +import seedu.address.testutil.PositionBuilder; +import seedu.address.testutil.PositionUtil; +import seedu.address.testutil.TypicalPersons; + +public class HrManagerParserTest { + + private final HrManagerParser parser = new HrManagerParser(); + + @Test + public void parseCommand_addCandidate() throws Exception { + Person person = new PersonBuilder().withRemark("").build(); + AddCandidateCommand command = + (AddCandidateCommand) parser.parseCommand(PersonUtil.getAddCandidateCommand(person)); + assertEquals(new AddCandidateCommand(person), command); + } + + @Test + public void parseCommand_addPosition() throws Exception { + Position position = new PositionBuilder().build(); + AddPositionCommand command = + (AddPositionCommand) parser.parseCommand(PositionUtil.getAddPositionCommand(position)); + assertEquals(new AddPositionCommand(position), command); + } + + @Test + public void parseCommand_addInterview() throws Exception { + //use the prompt message from the main app, a string as it is + String standardInput = "add_i position=Accountant " + + "c=1 date=15/10/2021 time=1400 duration=120 interviewed=pending"; + Command command = parser.parseCommand(standardInput); + assertTrue(command instanceof AddInterviewCommand); + + Interview interview = + new InterviewBuilder().withCandidates(new HashSet<>(List.of(TypicalPersons.BENSON))).build(); + Command otherCommand = parser.parseCommand(InterviewUtil.getAddInterviewCommand(interview)); + assertTrue(otherCommand instanceof AddInterviewCommand); + } + + @Test + public void parseCommand_clear() throws Exception { + assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD) instanceof ClearCommand); + assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD + " 3") instanceof ClearCommand); + } + + @Test + public void parseCommand_deleteCandidate() throws Exception { + DeleteCandidateCommand command = (DeleteCandidateCommand) parser.parseCommand( + DeleteCandidateCommand.COMMAND_WORD + " " + INDEX_FIRST_POSITION.getOneBased()); + assertEquals(new DeleteCandidateCommand(INDEX_FIRST_POSITION), command); + } + + @Test + public void parseCommand_deletePosition() throws Exception { + DeletePositionCommand command = (DeletePositionCommand) parser.parseCommand( + DeletePositionCommand.COMMAND_WORD + " " + INDEX_FIRST_POSITION.getOneBased()); + assertEquals(new DeletePositionCommand(INDEX_FIRST_POSITION), command); + } + + @Test + public void parseCommand_deleteInterview() throws Exception { + DeleteInterviewCommand command = (DeleteInterviewCommand) parser.parseCommand( + DeleteInterviewCommand.COMMAND_WORD + " " + INDEX_FIRST_INTERVIEW.getOneBased()); + assertEquals(new DeleteInterviewCommand(INDEX_FIRST_INTERVIEW), command); + } + + @Test + public void parseCommand_editCandidate() throws Exception { + Person person = new PersonBuilder().build(); + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build(); + EditCandidateCommand command = (EditCandidateCommand) + parser.parseCommand(EditCandidateCommand.COMMAND_WORD + + " " + INDEX_FIRST_POSITION.getOneBased() + + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor)); + assertEquals(new EditCandidateCommand(INDEX_FIRST_POSITION, descriptor), command); + } + + @Test + public void parseCommand_editInterview() throws Exception { + String userInput = EditInterviewCommand.COMMAND_WORD + " " + "1" + " " + + PREFIX_POSITION + VALID_POSITION_ADMIN_ASSISTANT + + " " + PREFIX_DATE + VALID_DATE + " " + PREFIX_TIME + VALID_TIME + " " + + " " + PREFIX_DURATION + VALID_DURATION_TIME + " " + PREFIX_INTERVIEW_STATUS + + VALID_STATUS_PENDING; + EditInterviewDescriptor descriptor = + new EditInterviewDescriptorBuilder() + .withPosition(VALID_POSITION_ADMIN_ASSISTANT) + .withDate(VALID_DATE) + .withStartTime(VALID_TIME) + .withDuration(VALID_DURATION_TIME) + .withStatus(VALID_STATUS_PENDING).build(); + EditInterviewCommand command = (EditInterviewCommand) + parser.parseCommand(userInput); + assertEquals(new EditInterviewCommand(INDEX_FIRST_INTERVIEW, descriptor), command); + } + + @Test + public void parseCommand_exit() throws Exception { + assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand); + assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand); + } + + @Test + public void parseCommand_remarkCandidate() throws Exception { + String exampleInput = RemarkCandidateCommand.COMMAND_WORD + " 3"; + String anotherInput = RemarkCandidateCommand.COMMAND_WORD + " 3 " + PREFIX_REMARK + VALID_REMARK_AMY; + assertTrue(parser.parseCommand(exampleInput) instanceof RemarkCandidateCommand); + assertTrue(parser.parseCommand(anotherInput) instanceof RemarkCandidateCommand); + } + + @Test + public void parseCommand_findCandidate() throws Exception { + List<String> keywords = Arrays.asList("foo", "bar", "baz"); + FindCandidateCommand command = (FindCandidateCommand) parser.parseCommand( + FindCandidateCommand.COMMAND_WORD + " " + + PREFIX_NAME + keywords.stream().collect(Collectors.joining(" "))); + assertEquals(new FindCandidateCommand(new FindCandidateCommandPredicate(keywords)), command); + } + + @Test + public void parseCommand_findPosition() throws Exception { + List<String> keywords = Arrays.asList("foo", "bar", "baz"); + FindPositionCommand command = (FindPositionCommand) parser.parseCommand( + FindPositionCommand.COMMAND_WORD + " " + + PREFIX_TITLE + keywords.stream().collect(Collectors.joining(" "))); + assertEquals(new FindPositionCommand(new FindPositionCommandPredicate(keywords)), command); + } + + @Test + public void parseCommand_help() throws Exception { + assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD) instanceof HelpCommand); + assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD + " 3") instanceof HelpCommand); + } + + @Test + public void parseCommand_candidateList() throws Exception { + assertTrue(parser.parseCommand(ListCandidateCommand.COMMAND_WORD) instanceof ListCandidateCommand); + assertTrue(parser.parseCommand(ListCandidateCommand.COMMAND_WORD + + " 3") instanceof ListCandidateCommand); + } + + @Test + public void parseCommand_positionList() throws Exception { + assertTrue(parser.parseCommand(ListPositionCommand.COMMAND_WORD) instanceof ListPositionCommand); + assertTrue(parser.parseCommand(ListPositionCommand.COMMAND_WORD + + " 3") instanceof ListPositionCommand); + } + + @Test + public void parseCommand_unrecognisedInput_throwsParseException() { + assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, + HelpCommand.MESSAGE_USAGE), () -> parser.parseCommand("")); + } + + @Test + public void parseCommand_unknownCommand_throwsParseException() { + assertThrows(ParseException.class, + MESSAGE_UNKNOWN_COMMAND, () -> parser.parseCommand("unknownCommand")); + } + + @Test + public void parseCommand_editPosition() throws Exception { + Position position = new PositionBuilder().build(); + EditPositionDescriptor descriptor = new EditPositionDescriptorBuilder(position).build(); + EditPositionCommand command = (EditPositionCommand) + parser.parseCommand(EditPositionCommand.COMMAND_WORD + + " " + INDEX_FIRST_POSITION.getOneBased() + + " " + PositionUtil.getEditPositionDescriptorDetails(descriptor)); + assertEquals(new EditPositionCommand(INDEX_FIRST_POSITION, descriptor), command); + } + + @Test + public void parseCommand_interviewList() throws Exception { + assertTrue(parser.parseCommand(ListInterviewCommand.COMMAND_WORD) instanceof ListInterviewCommand); + assertTrue(parser.parseCommand(ListInterviewCommand.COMMAND_WORD + + " 3") instanceof ListInterviewCommand); + } + + @Test + public void parseCommand_unassign() throws Exception { + //unassign 1 candidate + Command command = parser.parseCommand(UnassignInterviewCommand.COMMAND_WORD + " " + + PREFIX_INTERVIEW_INDEX + "1 " + PREFIX_CANDIDATE_INDEX + "1"); + assertEquals(new UnassignInterviewCommand(INDEX_FIRST_INTERVIEW, new HashSet<>()), command); + + //unassign all candidates + Command otherCommand = parser.parseCommand(UnassignInterviewCommand.COMMAND_WORD + " " + + PREFIX_INTERVIEW_INDEX + "1 " + PREFIX_CANDIDATE_INDEX + "*"); + assertEquals(new UnassignInterviewCommand(INDEX_FIRST_INTERVIEW, true), otherCommand); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 4256788b1a7..82210a49b30 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -5,6 +5,7 @@ import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; import java.util.Arrays; import java.util.Collections; @@ -13,11 +14,13 @@ import org.junit.jupiter.api.Test; +import seedu.address.commons.core.index.Index; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.position.Position.PositionStatus; import seedu.address.model.tag.Tag; public class ParserUtilTest { @@ -26,6 +29,8 @@ public class ParserUtilTest { private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; private static final String INVALID_TAG = "#friend"; + private static final String INVALID_TITLE = "Acc-Manager"; + private static final String INVALID_POSITION_STATUS = "status"; private static final String VALID_NAME = "Rachel Walker"; private static final String VALID_PHONE = "123456"; @@ -33,6 +38,8 @@ public class ParserUtilTest { private static final String VALID_EMAIL = "rachel@example.com"; private static final String VALID_TAG_1 = "friend"; private static final String VALID_TAG_2 = "neighbour"; + private static final String VALID_OPEN_POSITION_STATUS = "open"; + private static final String VALID_CLOSED_POSITION_STATUS = "closed"; private static final String WHITESPACE = " \t\r\n"; @@ -193,4 +200,91 @@ public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception { assertEquals(expectedTagSet, actualTagSet); } + + @Test + public void parseTitle_invalidTitle_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseTitle(INVALID_TITLE)); + } + + @Test + public void parseTitle_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseTitle((String) null)); + } + + @Test + public void parsePositionStatus_invalidStatus_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parsePositionStatus(INVALID_POSITION_STATUS)); + } + + @Test + public void parsePositionStatus_validStatusWithWhitespace_returnsTrimmedStatus() throws ParseException { + String statusWithWhitespace = WHITESPACE + VALID_OPEN_POSITION_STATUS + WHITESPACE; + PositionStatus expectedStatus = PositionStatus.OPEN; + + assertEquals(expectedStatus, ParserUtil.parsePositionStatus(statusWithWhitespace)); + } + + @Test + public void parsePositionStatus_validStatusWithoutWhitespace_returnsTrimmedStatus() throws ParseException { + PositionStatus expectedStatus = PositionStatus.CLOSED; + assertEquals(expectedStatus, ParserUtil.parsePositionStatus(VALID_CLOSED_POSITION_STATUS)); + } + + @Test + public void parsePositionStatus_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parsePositionStatus((String) null)); + } + + @Test + public void parseDuration_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseDuration("string")); + } + + @Test + public void parseDuration_invalidValueMoreThanOneDay_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseDuration("1440")); + assertThrows(ParseException.class, () -> ParserUtil.parseDuration(String.valueOf(Long.MAX_VALUE))); + } + + @Test + public void parseDuration_invalidValueTooShort_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseDuration("0")); + } + + @Test + public void parseDate_invalidValueNoPatternFound_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseDate("222/12/2021")); + } + + @Test + public void parseTime_invalidValueNoPatternFound_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseTime("22222")); + } + + @Test + public void parseCandidateIndexes_validValues_returnsCandidateIndexes() throws ParseException { + Set<Index> indexes = new HashSet<>(); + indexes.add(INDEX_FIRST_PERSON); + indexes.add(INDEX_SECOND_PERSON); + + Set<Index> test = ParserUtil.parseCandidateIndexes("2 1"); + + assertEquals(indexes, test); + } + + @Test + public void parseCandidateIndexes_duplicateValues_returnsCandidateIndexesWithoutDuplicates() throws ParseException { + Set<Index> indexes = new HashSet<>(); + indexes.add(INDEX_FIRST_PERSON); + indexes.add(INDEX_SECOND_PERSON); + + Set<Index> test = ParserUtil.parseCandidateIndexes("2 1 2 2 2 2 2 2 2 2 2 2 1 1"); + + assertEquals(indexes, test); + } + + @Test + public void parseCandidateIndexes_empty_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseCandidateIndexes("")); + } } diff --git a/src/test/java/seedu/address/logic/parser/RemarkCandidateCommandParserTest.java b/src/test/java/seedu/address/logic/parser/RemarkCandidateCommandParserTest.java new file mode 100644 index 00000000000..48cd5e78189 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/RemarkCandidateCommandParserTest.java @@ -0,0 +1,38 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_REMARK_BOB; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.candidate.RemarkCandidateCommand; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Remark; + +class RemarkCandidateCommandParserTest { + + private RemarkCandidateCommandParser parser = new RemarkCandidateCommandParser(); + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void execute_invalidPersonIndex_throwsCommandException() { + assertParseFailure(parser, model.getFilteredPersonList().size() + + VALID_REMARK_BOB, String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ParserUtil.MESSAGE_INVALID_INDEX) + RemarkCandidateCommand.MESSAGE_USAGE); + } + + @Test + public void execute_validPersonIndex_success() { + Index index = Index.fromOneBased(model.getFilteredPersonList().size() - 1); + RemarkCandidateCommand expectedCommand = new RemarkCandidateCommand(index, new Remark(VALID_REMARK_BOB)); + String userInput = " " + (index.getOneBased()) + " " + PREFIX_REMARK + VALID_REMARK_BOB; + assertParseSuccess(parser, userInput, expectedCommand); + } +} diff --git a/src/test/java/seedu/address/logic/parser/UnassignInterviewCommandParserTest.java b/src/test/java/seedu/address/logic/parser/UnassignInterviewCommandParserTest.java new file mode 100644 index 00000000000..a8e2c61acc8 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/UnassignInterviewCommandParserTest.java @@ -0,0 +1,47 @@ +package seedu.address.logic.parser; + + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_INTERVIEW; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.interview.UnassignInterviewCommand; + +public class UnassignInterviewCommandParserTest { + + private UnassignInterviewCommandParser parser = new UnassignInterviewCommandParser(); + + @Test + public void parse_validArgs_returnsUnassignCommand() { + Set<Index> indexes = new HashSet<>(); + indexes.add(INDEX_FIRST_PERSON); + indexes.add(INDEX_SECOND_PERSON); + + assertParseSuccess(parser, " i=1 c=1 2", new UnassignInterviewCommand(INDEX_FIRST_INTERVIEW, indexes)); + } + + @Test + public void parse_validArgsIsTotalWipe_returnsUnassignCommand() { + Set<Index> indexes = new HashSet<>(); + indexes.add(INDEX_FIRST_PERSON); + indexes.add(INDEX_SECOND_PERSON); + + assertParseSuccess(parser, " i=1 c=*", new UnassignInterviewCommand(INDEX_FIRST_INTERVIEW, true)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "i=a c=1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UnassignInterviewCommand.MESSAGE_USAGE)); + } + +} diff --git a/src/test/java/seedu/address/logic/position/AddPositionCommandIntegrationTest.java b/src/test/java/seedu/address/logic/position/AddPositionCommandIntegrationTest.java new file mode 100644 index 00000000000..220b9ab6b83 --- /dev/null +++ b/src/test/java/seedu/address/logic/position/AddPositionCommandIntegrationTest.java @@ -0,0 +1,39 @@ +package seedu.address.logic.position; + +import static seedu.address.logic.position.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.position.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.position.Position; +import seedu.address.testutil.PositionBuilder; + +public class AddPositionCommandIntegrationTest { + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + } + + @Test + public void execute_newPosition_success() { + Position validPosition = new PositionBuilder().build(); + Model expectedModel = new ModelManager(model.getHrManager(), new UserPrefs()); + expectedModel.addPosition(validPosition); + assertCommandSuccess(new AddPositionCommand(validPosition), model, + String.format(AddPositionCommand.MESSAGE_SUCCESS, validPosition), expectedModel); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() { + Position positionInList = model.getHrManager().getPositionList().get(0); + assertCommandFailure(new AddPositionCommand(positionInList), model, + AddPositionCommand.MESSAGE_DUPLICATE_PERSON); + } +} diff --git a/src/test/java/seedu/address/logic/position/AddPositionCommandTest.java b/src/test/java/seedu/address/logic/position/AddPositionCommandTest.java new file mode 100644 index 00000000000..a27ec116038 --- /dev/null +++ b/src/test/java/seedu/address/logic/position/AddPositionCommandTest.java @@ -0,0 +1,92 @@ +package seedu.address.logic.position; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.testutil.Assert.assertThrows; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.HrManager; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.model.position.Position; +import seedu.address.testutil.ModelStub; +import seedu.address.testutil.PositionBuilder; + + +public class AddPositionCommandTest { + + @Test + public void constructor_nullPosition_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new AddPositionCommand(null)); + } + + @Test + public void execute_positionAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingPositionAdded modelStub = new AddPositionCommandTest + .ModelStubAcceptingPositionAdded(); + Position validPosition = new PositionBuilder().build(); + + CommandResult commandResult = new AddPositionCommand(validPosition).execute(modelStub); + + assertEquals(String.format(AddPositionCommand.MESSAGE_SUCCESS, validPosition), + commandResult.getFeedbackToUser()); + assertEquals(Arrays.asList(validPosition), modelStub.positionsAdded); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() { + Position validPosition = new PositionBuilder().build(); + AddPositionCommand addPositionCommand = new AddPositionCommand(validPosition); + ModelStubWithPosition modelStub = new ModelStubWithPosition(validPosition); + + assertThrows(CommandException.class, AddPositionCommand.MESSAGE_DUPLICATE_PERSON, () -> + addPositionCommand.execute(modelStub)); + } + + /** + * A Model stub that contains a single person. + */ + private class ModelStubWithPosition extends ModelStub { + private final Position position; + + ModelStubWithPosition(Position position) { + requireNonNull(position); + this.position = position; + } + + @Override + public boolean hasPosition(Position position) { + requireNonNull(position); + return this.position.isSamePosition(position); + } + } + + /** + * A Model stub that always accept the person being added. + */ + private class ModelStubAcceptingPositionAdded extends ModelStub { + final ArrayList<Position> positionsAdded = new ArrayList<>(); + + @Override + public boolean hasPosition(Position position) { + requireNonNull(position); + return positionsAdded.stream().anyMatch(position::isSamePosition); + } + + @Override + public void addPosition(Position position) { + requireNonNull(position); + positionsAdded.add(position); + } + + @Override + public ReadOnlyHrManager getHrManager() { + return new HrManager(); + } + } +} diff --git a/src/test/java/seedu/address/logic/position/CommandTestUtil.java b/src/test/java/seedu/address/logic/position/CommandTestUtil.java new file mode 100644 index 00000000000..8b203adf809 --- /dev/null +++ b/src/test/java/seedu/address/logic/position/CommandTestUtil.java @@ -0,0 +1,159 @@ +package seedu.address.logic.position; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; +import static seedu.address.testutil.Assert.assertThrows; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.model.HrManager; +import seedu.address.model.Model; +import seedu.address.model.position.Position; +import seedu.address.model.position.Position.PositionStatus; +import seedu.address.model.position.TitleContainsKeywordsPredicate; +import seedu.address.testutil.EditPositionDescriptorBuilder; + +public class CommandTestUtil { + + public static final String VALID_TITLE_BOOKKEEPER = "Bookkeeper"; + public static final String VALID_TITLE_HR_MANAGER = "HR Manager"; + public static final String VALID_TITLE_ADMIN_ASSISTANT = "Administrative Assistant"; + public static final PositionStatus VALID_STATUS_OPEN = PositionStatus.OPEN; + public static final PositionStatus VALID_STATUS_CLOSED = PositionStatus.CLOSED; + + public static final String POSITION_HR_MANAGER = " " + PREFIX_TITLE + VALID_TITLE_HR_MANAGER; + public static final String POSITION_ADMIN_ASSISTANT = " " + PREFIX_TITLE + VALID_TITLE_ADMIN_ASSISTANT; + public static final String POSITION_BOOKKEEPER = " " + PREFIX_TITLE + VALID_TITLE_BOOKKEEPER; + public static final String STATUS_OPEN = " " + PREFIX_POSITION_STATUS + "open"; + + public static final String INVALID_POSITION_TITLE = " " + PREFIX_TITLE + "!accountant"; + public static final String INVALID_POSITION_STATUS = " " + PREFIX_POSITION_STATUS + "status"; + + public static final String PREAMBLE_WHITESPACE = "\t \r \n"; + public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; + + public static final EditPositionCommand.EditPositionDescriptor DESC_BOOKKEEPER; + public static final EditPositionCommand.EditPositionDescriptor DESC_HR_MANAGER; + public static final EditPositionCommand.EditPositionDescriptor DESC_ADMIN_ASSISTANT; + + static { + DESC_BOOKKEEPER = new EditPositionDescriptorBuilder().withTitle(VALID_TITLE_BOOKKEEPER) + .withPositionStatus(VALID_STATUS_OPEN).build(); + DESC_HR_MANAGER = new EditPositionDescriptorBuilder().withTitle(VALID_TITLE_HR_MANAGER) + .withPositionStatus(VALID_STATUS_OPEN).build(); + DESC_ADMIN_ASSISTANT = new EditPositionDescriptorBuilder().withTitle(VALID_TITLE_ADMIN_ASSISTANT) + .withPositionStatus(VALID_STATUS_CLOSED).build(); + } + + /** + * Executes the given {@code command}, confirms that <br> + * - the returned {@link CommandResult} matches {@code expectedCommandResult} <br> + * - the {@code actualModel} matches {@code expectedModel} + */ + public static void assertCommandSuccess(Command command, Model actualModel, CommandResult expectedCommandResult, + Model expectedModel) { + try { + CommandResult result = command.execute(actualModel); + assertEquals(expectedCommandResult, result); + assertEquals(expectedModel, actualModel); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} + * that takes a string {@code expectedMessage}. + */ + public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + CommandResult expectedCommandResult = new CommandResult(expectedMessage, CommandResult.CommandType.POSITION); + assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); + } + + private static boolean isEditPCommand(Command command) { + String commandClassName = command.getClass().getSimpleName(); + return commandClassName.equals("EditPositionCommand"); + } + + /** + * Executes the given {@code command}, confirms that <br> + * - the given {@code command} is an edit command <br> + * - the returned {@link CommandResult} matches {@code expectedCommandResult} <br> + * - the {@code actualModel} matches {@code expectedModel} + */ + public static void assertEditPositionCommandSuccess(Command command, Model actualModel, + CommandResult expectedEditCommandResult, Model expectedModel) { + try { + CommandResult result = command.execute(actualModel); + assertEquals(expectedEditCommandResult, result); + assertEquals(expectedModel, actualModel); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Convenience wrapper to {@link #assertEditPositionCommandSuccess(Command, Model, CommandResult, Model)} + * that takes a string {@code expectedMessage}. + */ + public static void assertEditPositionCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + if (!isEditPCommand(command)) { + throw new AssertionError("Command should be an EditPositionCommand"); + } + + CommandResult expectedCommandResult = new CommandResult(expectedMessage, + CommandResult.CommandType.POSITION); + assertEditPositionCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); + } + + /** + * Convenience wrapper to {@link #assertCommandSuccess(Command, Model, CommandResult, Model)} + * that takes a string {@code expectedMessage}. + */ + public static void assertListCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + CommandResult expectedCommandResult = new CommandResult(expectedMessage, CommandResult.CommandType.LIST_P); + assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel); + } + + /** + * Executes the given {@code command}, confirms that <br> + * - a {@code CommandException} is thrown <br> + * - the CommandException message matches {@code expectedMessage} <br> + * - the address book, filtered person list and selected person in {@code actualModel} remain unchanged + */ + public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) { + // we are unable to defensively copy the model for comparison later, so we can + // only do so by copying its components. + HrManager expectedHrManager = new HrManager(actualModel.getHrManager()); + List<Position> expectedFilteredList = new ArrayList<>(actualModel.getFilteredPositionList()); + + assertThrows(CommandException.class, expectedMessage, () -> command.execute(actualModel)); + assertEquals(expectedHrManager, actualModel.getHrManager()); + assertEquals(expectedFilteredList, actualModel.getFilteredPositionList()); + } + + /** + * Updates {@code model}'s filtered list to show only the position at the given {@code targetIndex} in the + * {@code model}'s HrManager. + */ + public static void showPositionAtIndex(Model model, Index targetIndex) { + assertTrue(targetIndex.getZeroBased() < model.getFilteredPositionList().size()); + + Position position = model.getFilteredPositionList().get(targetIndex.getZeroBased()); + final String[] splitTitle = position.getTitle().fullTitle.split("\\s+"); + model.updateFilteredPositionList(new TitleContainsKeywordsPredicate(Arrays.asList(splitTitle[0]))); + + assertEquals(1, model.getFilteredPositionList().size()); + } +} diff --git a/src/test/java/seedu/address/logic/position/DeletePositionCommandTest.java b/src/test/java/seedu/address/logic/position/DeletePositionCommandTest.java new file mode 100644 index 00000000000..0250f1f438d --- /dev/null +++ b/src/test/java/seedu/address/logic/position/DeletePositionCommandTest.java @@ -0,0 +1,106 @@ +package seedu.address.logic.position; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.position.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.position.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.position.CommandTestUtil.showPositionAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_POSITION; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_POSITION; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.position.Position; + +/** + * Contains integration tests (interaction with the Model) and unit tests for + * {@code DeletePositionCommand}. + */ +class DeletePositionCommandTest { + + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() { + Position positionToDelete = model.getFilteredPositionList().get(INDEX_FIRST_POSITION.getZeroBased()); + DeletePositionCommand deletePositionCommand = new DeletePositionCommand(INDEX_FIRST_POSITION); + + String expectedMessage = String.format(DeletePositionCommand.MESSAGE_DELETE_POSITION_SUCCESS, positionToDelete); + + ModelManager expectedModel = new ModelManager(model.getHrManager(), new UserPrefs()); + expectedModel.deletePosition(positionToDelete); + + assertCommandSuccess(deletePositionCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPositionList().size() + 1); + DeletePositionCommand deletePositionCommand = new DeletePositionCommand(outOfBoundIndex); + + assertCommandFailure(deletePositionCommand, model, Messages.MESSAGE_INVALID_POSITION_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexFilteredList_success() { + showPositionAtIndex(model, INDEX_FIRST_POSITION); + + Position positionToDelete = model.getFilteredPositionList().get(INDEX_FIRST_POSITION.getZeroBased()); + DeletePositionCommand deletePositionCommand = new DeletePositionCommand(INDEX_FIRST_POSITION); + + String expectedMessage = String.format(DeletePositionCommand.MESSAGE_DELETE_POSITION_SUCCESS, positionToDelete); + Model expectedModel = new ModelManager(getTypicalHrManager(), new UserPrefs()); + expectedModel.deletePosition(positionToDelete); + showNoPosition(expectedModel); + assertCommandSuccess(deletePositionCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showPositionAtIndex(model, INDEX_FIRST_POSITION); + + Index outOfBoundIndex = INDEX_SECOND_POSITION; + // ensures that outOfBoundIndex is still in bounds of HrManager's list + assertTrue(outOfBoundIndex.getZeroBased() < model.getHrManager().getPositionList().size()); + + DeletePositionCommand deletePositionCommand = new DeletePositionCommand(outOfBoundIndex); + + assertCommandFailure(deletePositionCommand, model, Messages.MESSAGE_INVALID_POSITION_DISPLAYED_INDEX); + } + + @Test + public void equals() { + DeletePositionCommand deleteFirstCommand = new DeletePositionCommand(INDEX_FIRST_POSITION); + DeletePositionCommand deleteSecondCommand = new DeletePositionCommand(INDEX_SECOND_POSITION); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + DeletePositionCommand deleteFirstCommandCopy = new DeletePositionCommand(INDEX_FIRST_POSITION); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different position -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } + + /** + * Updates {@code model}'s filtered list to show no one. + */ + private void showNoPosition(Model model) { + model.updateFilteredPositionList(p -> false); + assertTrue(model.getFilteredPositionList().isEmpty()); + } +} diff --git a/src/test/java/seedu/address/logic/position/EditPositionCommandTest.java b/src/test/java/seedu/address/logic/position/EditPositionCommandTest.java new file mode 100644 index 00000000000..15099569704 --- /dev/null +++ b/src/test/java/seedu/address/logic/position/EditPositionCommandTest.java @@ -0,0 +1,310 @@ +package seedu.address.logic.position; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.position.CommandTestUtil.DESC_ADMIN_ASSISTANT; +import static seedu.address.logic.position.CommandTestUtil.DESC_BOOKKEEPER; +import static seedu.address.logic.position.CommandTestUtil.VALID_STATUS_OPEN; +import static seedu.address.logic.position.CommandTestUtil.VALID_TITLE_ADMIN_ASSISTANT; +import static seedu.address.logic.position.CommandTestUtil.VALID_TITLE_BOOKKEEPER; +import static seedu.address.logic.position.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.position.CommandTestUtil.assertEditPositionCommandSuccess; +import static seedu.address.logic.position.CommandTestUtil.showPositionAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_POSITION; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_POSITION; +import static seedu.address.testutil.TypicalPersons.JOHN; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; +import static seedu.address.testutil.TypicalPositions.BOOKKEEPER; +import static seedu.address.testutil.TypicalPositions.CLOSED_POSITION_CLERK; + +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandResult; +import seedu.address.logic.candidate.exceptions.CommandException; +import seedu.address.logic.general.ClearCommand; +import seedu.address.model.HrManager; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; +import seedu.address.testutil.EditPositionDescriptorBuilder; +import seedu.address.testutil.ModelStub; +import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.PositionBuilder; + +/** + * Contains integration tests (interaction with the Model) and unit tests for EditPositionCommand. + */ +public class EditPositionCommandTest { + + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void execute_titleSpecifiedUnfilteredList_success() { + Position editedPosition = + new PositionBuilder().withTitle("Business Analyst").withStatus(VALID_STATUS_OPEN).build(); + EditPositionCommand.EditPositionDescriptor descriptor = + new EditPositionDescriptorBuilder().withTitle(editedPosition.getTitle().fullTitle).build(); + EditPositionCommand editPositionCommand = new EditPositionCommand(INDEX_FIRST_POSITION, descriptor); + + String expectedMessage = String.format(EditPositionCommand.MESSAGE_EDIT_POSITION_SUCCESS, editedPosition); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + expectedModel.setPosition(model.getFilteredPositionList().get(0), editedPosition); + + assertEditPositionCommandSuccess(editPositionCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_statusSpecifiedUnfilteredList_success() { + Position editedPosition = + new PositionBuilder().withTitle("Administrative Assistant").withStatus(VALID_STATUS_OPEN).build(); + EditPositionCommand.EditPositionDescriptor descriptor = + new EditPositionDescriptorBuilder().withPositionStatus(editedPosition.getStatus()).build(); + EditPositionCommand editPositionCommand = new EditPositionCommand(INDEX_FIRST_POSITION, descriptor); + + String expectedMessage = String.format(EditPositionCommand.MESSAGE_EDIT_POSITION_SUCCESS, editedPosition); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + expectedModel.setPosition(model.getFilteredPositionList().get(0), editedPosition); + + assertEditPositionCommandSuccess(editPositionCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_noFieldSpecifiedUnfilteredList_success() { + EditPositionCommand editPositionCommand = new EditPositionCommand(INDEX_FIRST_POSITION, + new EditPositionCommand.EditPositionDescriptor()); + Position editedPosition = model.getFilteredPositionList().get(INDEX_FIRST_POSITION.getZeroBased()); + + String expectedMessage = String.format(EditPositionCommand.MESSAGE_EDIT_POSITION_SUCCESS, editedPosition); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + + assertEditPositionCommandSuccess(editPositionCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_filteredList_success() { + showPositionAtIndex(model, INDEX_FIRST_POSITION); + + Position positionInFilteredList = model.getFilteredPositionList().get(INDEX_FIRST_POSITION.getZeroBased()); + Position editedPosition = + new PositionBuilder(positionInFilteredList).withTitle(VALID_TITLE_ADMIN_ASSISTANT).build(); + EditPositionCommand editPositionCommand = new EditPositionCommand(INDEX_FIRST_POSITION, + new EditPositionDescriptorBuilder().withTitle(VALID_TITLE_ADMIN_ASSISTANT).build()); + + String expectedMessage = String.format(EditPositionCommand.MESSAGE_EDIT_POSITION_SUCCESS, editedPosition); + + Model expectedModel = new ModelManager(new HrManager(model.getHrManager()), new UserPrefs()); + expectedModel.setPosition(model.getFilteredPositionList().get(0), editedPosition); + showPositionAtIndex(expectedModel, INDEX_FIRST_POSITION); + + assertEditPositionCommandSuccess(editPositionCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_duplicatePositionUnfilteredList_failure() { + Position firstPosition = model.getFilteredPositionList().get(INDEX_FIRST_POSITION.getZeroBased()); + EditPositionCommand.EditPositionDescriptor descriptor = + new EditPositionDescriptorBuilder().withTitle(firstPosition.getTitle().fullTitle).build(); + EditPositionCommand editPositionCommand = new EditPositionCommand(INDEX_SECOND_POSITION, descriptor); + + assertCommandFailure(editPositionCommand, model, EditPositionCommand.MESSAGE_DUPLICATE_POSITION); + } + + @Test + public void execute_duplicatePositionFilteredList_failure() { + showPositionAtIndex(model, INDEX_FIRST_POSITION); + + // edit position in filtered list into a duplicate in HR Manager + Position positionInList = model.getHrManager().getPositionList().get(INDEX_SECOND_POSITION.getZeroBased()); + EditPositionCommand editPositionCommand = new EditPositionCommand(INDEX_FIRST_POSITION, + new EditPositionDescriptorBuilder().withTitle(positionInList.getTitle().fullTitle).build()); + + assertCommandFailure(editPositionCommand, model, EditPositionCommand.MESSAGE_DUPLICATE_POSITION); + } + + @Test + public void execute_invalidPositionIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPositionList().size() + 1); + EditPositionCommand.EditPositionDescriptor descriptor = + new EditPositionDescriptorBuilder().withTitle(VALID_TITLE_BOOKKEEPER).build(); + EditPositionCommand editPositionCommand = new EditPositionCommand(outOfBoundIndex, descriptor); + + assertCommandFailure(editPositionCommand, model, Messages.MESSAGE_INVALID_POSITION_DISPLAYED_INDEX); + } + + /** + * Edit filtered list where index is larger than size of filtered list, + * but smaller than size of position list + */ + @Test + public void execute_invalidPositionIndexFilteredList_failure() { + showPositionAtIndex(model, INDEX_FIRST_POSITION); + Index outOfBoundIndex = INDEX_SECOND_POSITION; + // ensures that outOfBoundIndex is still in bounds of position list + assertTrue(outOfBoundIndex.getZeroBased() < model.getHrManager().getPositionList().size()); + + EditPositionCommand editPositionCommand = new EditPositionCommand(outOfBoundIndex, + new EditPositionDescriptorBuilder().withTitle(VALID_TITLE_BOOKKEEPER).build()); + + assertCommandFailure(editPositionCommand, model, Messages.MESSAGE_INVALID_POSITION_DISPLAYED_INDEX); + } + + @Test + public void execute_bothFieldsEdited_failure() { + EditPositionCommand.EditPositionDescriptor descriptor = new EditPositionDescriptorBuilder() + .withTitle("Business Analyst") + .withPositionStatus(VALID_STATUS_OPEN).build(); + EditPositionCommand editPositionCommand = new EditPositionCommand(INDEX_FIRST_POSITION, descriptor); + assertCommandFailure(editPositionCommand, model, EditPositionCommand.MESSAGE_BOTH_FIELDS_EDITED); + } + + @Test + public void equals() { + final EditPositionCommand standardCommand = new EditPositionCommand(INDEX_FIRST_POSITION, DESC_ADMIN_ASSISTANT); + + // same values -> returns true + EditPositionCommand.EditPositionDescriptor copyDescriptor = + new EditPositionCommand.EditPositionDescriptor(DESC_ADMIN_ASSISTANT); + EditPositionCommand commandWithSameValues = new EditPositionCommand(INDEX_FIRST_POSITION, copyDescriptor); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(standardCommand.equals(new EditPositionCommand(INDEX_SECOND_POSITION, DESC_ADMIN_ASSISTANT))); + + // different descriptor -> returns false + assertFalse(standardCommand.equals(new EditPositionCommand(INDEX_FIRST_POSITION, DESC_BOOKKEEPER))); + } + + @Test + public void execute_someFieldsSpecified_success() { + ModelStubAcceptingPositionEdited modelStub = new ModelStubAcceptingPositionEdited(); + + Position editedPosition = + new PositionBuilder().withTitle("Business Analyst").build(); + EditPositionCommand.EditPositionDescriptor descriptor = + new EditPositionDescriptorBuilder().withTitle(editedPosition.getTitle().fullTitle).build(); + EditPositionCommand editPositionCommand = new EditPositionCommand(INDEX_FIRST_POSITION, descriptor); + + String expectedMessage = String.format(EditPositionCommand.MESSAGE_EDIT_POSITION_SUCCESS, editedPosition); + + assertEditSuccess(editPositionCommand, expectedMessage, modelStub); + + //check that person with corresponding position is edited + Person expectedPerson = new PersonBuilder(JOHN).withPositions("Business Analyst", + CLOSED_POSITION_CLERK.getTitle().fullTitle).build(); + assertEquals(expectedPerson, modelStub.getPerson(INDEX_FIRST_PERSON)); + } + + private void assertEditSuccess(EditPositionCommand command, String expectedMessage, Model model) { + try { + CommandResult result = command.execute(model); + //because standard constructor of CommandResult does not update isListP to true + assertEquals(result, new CommandResult(expectedMessage, + CommandResult.CommandType.POSITION)); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * A Model stub that always accept the person being added. + */ + private class ModelStubAcceptingPositionEdited extends ModelStub { + final ObservableList<Position> positions = FXCollections.observableArrayList(); + final ObservableList<Person> persons = FXCollections.observableArrayList(); + final ObservableList<Interview> interviews = FXCollections.observableArrayList(); + + ModelStubAcceptingPositionEdited() { + persons.add(JOHN); + positions.add(BOOKKEEPER); + positions.add(CLOSED_POSITION_CLERK); + } + + @Override + public boolean hasPosition(Position position) { + requireNonNull(position); + return positions.stream().anyMatch(position::isSamePosition); + } + + @Override + public Position getPositionReference(Position position) { + return position; + } + + @Override + public void addPosition(Position position) { + requireNonNull(position); + positions.add(position); + } + + @Override + public void setPosition(Position target, Position editedPosition) { + int index = positions.indexOf(target); + positions.set(index, editedPosition); + } + + @Override + public Person getPerson(Index index) { + return persons.get(index.getZeroBased()); + } + + @Override + public ObservableList<Position> getFilteredPositionList() { + return positions; + } + + @Override + public ObservableList<Person> getFilteredPersonList() { + return persons; + } + + @Override + public ObservableList<Interview> getFilteredInterviewList() { + return interviews; + } + + @Override + public void setPerson(Person target, Person editedPerson) { + int index = persons.indexOf(target); + persons.set(index, editedPerson); + } + + @Override + public ReadOnlyHrManager getHrManager() { + return new HrManager(); + } + + @Override + public void updateFilteredPositionList(Predicate<Position> predicate) { + } + + @Override + public void updateFilteredPersonList(Predicate<Person> predicate) { + } + } + +} diff --git a/src/test/java/seedu/address/logic/position/EditPositionDescriptorTest.java b/src/test/java/seedu/address/logic/position/EditPositionDescriptorTest.java new file mode 100644 index 00000000000..b112b6fc490 --- /dev/null +++ b/src/test/java/seedu/address/logic/position/EditPositionDescriptorTest.java @@ -0,0 +1,46 @@ +package seedu.address.logic.position; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.position.CommandTestUtil.DESC_ADMIN_ASSISTANT; +import static seedu.address.logic.position.CommandTestUtil.DESC_BOOKKEEPER; +import static seedu.address.logic.position.CommandTestUtil.VALID_STATUS_OPEN; +import static seedu.address.logic.position.CommandTestUtil.VALID_TITLE_BOOKKEEPER; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.EditPositionDescriptorBuilder; + +public class EditPositionDescriptorTest { + + @Test + public void equals() { + // same values -> returns true + EditPositionCommand.EditPositionDescriptor descriptorWithSameValues = + new EditPositionCommand.EditPositionDescriptor(DESC_ADMIN_ASSISTANT); + assertTrue(DESC_ADMIN_ASSISTANT.equals(descriptorWithSameValues)); + + // same object -> returns true + assertTrue(DESC_ADMIN_ASSISTANT.equals(DESC_ADMIN_ASSISTANT)); + + // null -> returns false + assertFalse(DESC_ADMIN_ASSISTANT.equals(null)); + + // different types -> returns false + assertFalse(DESC_ADMIN_ASSISTANT.equals(5)); + + // different values -> returns false + assertFalse(DESC_ADMIN_ASSISTANT.equals(DESC_BOOKKEEPER)); + + // different title -> returns false + EditPositionCommand.EditPositionDescriptor editedAdminAssistant = + new EditPositionDescriptorBuilder(DESC_ADMIN_ASSISTANT).withTitle(VALID_TITLE_BOOKKEEPER).build(); + assertFalse(DESC_ADMIN_ASSISTANT.equals(editedAdminAssistant)); + + // different status -> returns false + editedAdminAssistant = + new EditPositionDescriptorBuilder(DESC_ADMIN_ASSISTANT).withPositionStatus(VALID_STATUS_OPEN).build(); + assertFalse(DESC_ADMIN_ASSISTANT.equals(editedAdminAssistant)); + + } +} diff --git a/src/test/java/seedu/address/logic/position/FindPositionCommandTest.java b/src/test/java/seedu/address/logic/position/FindPositionCommandTest.java new file mode 100644 index 00000000000..4e8cc4290b8 --- /dev/null +++ b/src/test/java/seedu/address/logic/position/FindPositionCommandTest.java @@ -0,0 +1,94 @@ +package seedu.address.logic.position; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_POSITIONS_LISTED_OVERVIEW; +import static seedu.address.logic.position.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.Command; +import seedu.address.logic.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.position.FindPositionCommandPredicate; + +/** + * Contains integration tests (interaction with the Model) for {@code FindCommand}. + */ +public class FindPositionCommandTest { + private Model model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalHrManager(), new UserPrefs()); + + @Test + public void equals() { + FindPositionCommandPredicate firstPredicate = + new FindPositionCommandPredicate(Collections.singletonList("first")); + FindPositionCommandPredicate secondPredicate = + new FindPositionCommandPredicate(Collections.singletonList("second")); + + FindPositionCommand findFirstCommand = new FindPositionCommand(firstPredicate); + FindPositionCommand findSecondCommand = new FindPositionCommand(secondPredicate); + + // same object -> returns true + assertTrue(findFirstCommand.equals(findFirstCommand)); + + // same values -> returns true + FindPositionCommand findFirstCommandCopy = new FindPositionCommand(firstPredicate); + assertTrue(findFirstCommand.equals(findFirstCommandCopy)); + + // different types -> returns false + assertFalse(findFirstCommand.equals(1)); + + // null -> returns false + assertFalse(findFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(findFirstCommand.equals(findSecondCommand)); + } + + @Test + public void execute_multipleKeywords_multiplePositionsFound() { + String expectedMessage = String.format(MESSAGE_POSITIONS_LISTED_OVERVIEW, 1); + FindPositionCommandPredicate predicate = preparePredicate("Assistant"); + FindPositionCommand command = new FindPositionCommand(predicate); + expectedModel.updateFilteredPositionList(predicate); + assertEquals(Arrays.asList(ADMIN_ASSISTANT), expectedModel.getFilteredPositionList()); + CommandResult expectedCommandResult = new CommandResult(expectedMessage, CommandResult.CommandType.FIND_P); + + assertCommandSuccess(command, model, expectedCommandResult, expectedModel); + } + + @Test + public void execute_positionDoesNotExist_noPositionFound() { + String expectedMessage = String.format(MESSAGE_POSITIONS_LISTED_OVERVIEW, 0); + FindPositionCommandPredicate predicate = preparePredicate("Recruiter"); + Command command = new FindPositionCommand(predicate); + expectedModel.updateFilteredPositionList(predicate); + assertEquals(Arrays.asList(), expectedModel.getFilteredPositionList()); + + //have to create a CommandResult manually because assertSuccess uses single parameter constructor + CommandResult expectedCommandResult = new CommandResult(expectedMessage, + CommandResult.CommandType.FIND_P); + + assertCommandSuccess(command, model, expectedCommandResult, expectedModel); + } + + /** + * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}. + */ + private FindPositionCommandPredicate preparePredicate(String userInput) { + List<String> output = new ArrayList<String>(Arrays.asList(userInput.split("\\s+"))); + output.removeAll(Arrays.asList("", null)); + return new FindPositionCommandPredicate(output); + } +} diff --git a/src/test/java/seedu/address/logic/position/ListPositionCommandTest.java b/src/test/java/seedu/address/logic/position/ListPositionCommandTest.java new file mode 100644 index 00000000000..100312e2039 --- /dev/null +++ b/src/test/java/seedu/address/logic/position/ListPositionCommandTest.java @@ -0,0 +1,39 @@ +package seedu.address.logic.position; + +import static seedu.address.logic.position.CommandTestUtil.assertListCommandSuccess; +import static seedu.address.logic.position.CommandTestUtil.showPositionAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_POSITION; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +/** + * Contains integration tests (interaction with the Model) and unit tests for ListPositionCommand. + */ +public class ListPositionCommandTest { + + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalHrManager(), new UserPrefs()); + expectedModel = new ModelManager(model.getHrManager(), new UserPrefs()); + } + + @Test + public void execute_listIsNotFiltered_showsSameList() { + assertListCommandSuccess(new ListPositionCommand(), model, ListPositionCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_listIsFiltered_showsEverything() { + showPositionAtIndex(model, INDEX_FIRST_POSITION); + assertListCommandSuccess(new ListPositionCommand(), model, ListPositionCommand.MESSAGE_SUCCESS, expectedModel); + } +} diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java deleted file mode 100644 index 87782528ecd..00000000000 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package seedu.address.model; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.testutil.PersonBuilder; - -public class AddressBookTest { - - private final AddressBook addressBook = new AddressBook(); - - @Test - public void constructor() { - assertEquals(Collections.emptyList(), addressBook.getPersonList()); - } - - @Test - public void resetData_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> addressBook.resetData(null)); - } - - @Test - public void resetData_withValidReadOnlyAddressBook_replacesData() { - AddressBook newData = getTypicalAddressBook(); - addressBook.resetData(newData); - assertEquals(newData, addressBook); - } - - @Test - public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { - // Two persons with the same identity fields - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - List<Person> newPersons = Arrays.asList(ALICE, editedAlice); - AddressBookStub newData = new AddressBookStub(newPersons); - - assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData)); - } - - @Test - public void hasPerson_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> addressBook.hasPerson(null)); - } - - @Test - public void hasPerson_personNotInAddressBook_returnsFalse() { - assertFalse(addressBook.hasPerson(ALICE)); - } - - @Test - public void hasPerson_personInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - assertTrue(addressBook.hasPerson(ALICE)); - } - - @Test - public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - assertTrue(addressBook.hasPerson(editedAlice)); - } - - @Test - public void getPersonList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () -> addressBook.getPersonList().remove(0)); - } - - /** - * A stub ReadOnlyAddressBook whose persons list can violate interface constraints. - */ - private static class AddressBookStub implements ReadOnlyAddressBook { - private final ObservableList<Person> persons = FXCollections.observableArrayList(); - - AddressBookStub(Collection<Person> persons) { - this.persons.setAll(persons); - } - - @Override - public ObservableList<Person> getPersonList() { - return persons; - } - } - -} diff --git a/src/test/java/seedu/address/model/HrManagerTest.java b/src/test/java/seedu/address/model/HrManagerTest.java new file mode 100644 index 00000000000..41da0b2e460 --- /dev/null +++ b/src/test/java/seedu/address/model/HrManagerTest.java @@ -0,0 +1,250 @@ +package seedu.address.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalInterviews.ASSISTANT_INTERVIEW; +import static seedu.address.testutil.TypicalInterviews.HR_MANAGER_INTERVIEW; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.interview.Interview; +import seedu.address.model.interview.exceptions.DuplicateInterviewException; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; +import seedu.address.model.position.exceptions.DuplicatePositionException; +import seedu.address.testutil.InterviewBuilder; +import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.PositionBuilder; + +public class HrManagerTest { + + private final HrManager hrManager = new HrManager(); + + @Test + public void constructor() { + assertEquals(Collections.emptyList(), hrManager.getPersonList()); + assertEquals(Collections.emptyList(), hrManager.getPositionList()); + assertEquals(Collections.emptyList(), hrManager.getInterviewList()); + } + + @Test + public void resetData_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> hrManager.resetData(null)); + } + + @Test + public void resetData_withValidReadOnlyHrManager_replacesData() { + HrManager newData = getTypicalHrManager(); + hrManager.resetData(newData); + assertEquals(newData, hrManager); + } + + @Test + public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { + // Two persons with the same identity fields + Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + .build(); + List<Person> newPersons = Arrays.asList(ALICE, editedAlice); + HrManagerStub newData = new HrManagerStub(() -> newPersons); + + assertThrows(DuplicatePersonException.class, () -> hrManager.resetData(newData)); + } + + @Test + public void resetData_withDuplicatePositions_throwsDuplicatePositionException() { + // Two positions with the same identity fields + Position editedAssistant = new PositionBuilder(ADMIN_ASSISTANT).build(); + List<Position> newPositions = Arrays.asList(ADMIN_ASSISTANT, editedAssistant); + HrManagerStub newData = new HrManagerStub(() -> newPositions); + + assertThrows(DuplicatePositionException.class, () -> hrManager.resetData(newData)); + } + + @Test + public void resetData_withDuplicateInterviews_throwsDuplicateInterviewException() { + // Two interviews with the same identity fields + Interview editedAssistantInterview = new InterviewBuilder().build(); + List<Interview> newInterviews = Arrays.asList(HR_MANAGER_INTERVIEW, editedAssistantInterview); + HrManagerStub newData = new HrManagerStub(() -> newInterviews); + + assertThrows(DuplicateInterviewException.class, () -> hrManager.resetData(newData)); + } + + //// person list + + @Test + public void hasPerson_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> hrManager.hasPerson(null)); + } + + @Test + public void hasPerson_personNotInHrManager_returnsFalse() { + assertFalse(hrManager.hasPerson(ALICE)); + } + + @Test + public void hasPerson_personInHrManager_returnsTrue() { + hrManager.addPerson(ALICE); + assertTrue(hrManager.hasPerson(ALICE)); + } + + @Test + public void hasPerson_personWithSameIdentityFieldsInHrManager_returnsTrue() { + hrManager.addPerson(ALICE); + Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + .build(); + assertTrue(hrManager.hasPerson(editedAlice)); + } + + @Test + public void getPersonList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> hrManager.getPersonList().remove(0)); + } + + //// position list + + @Test + public void hasPosition_nullPosition_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> hrManager.hasPosition(null)); + } + + @Test + public void hasPosition_positionNotInHrManager_returnsFalse() { + assertFalse(hrManager.hasPosition(ADMIN_ASSISTANT)); + } + + @Test + public void hasPosition_positionInHrManager_returnsTrue() { + hrManager.addPosition(ADMIN_ASSISTANT); + assertTrue(hrManager.hasPosition(ADMIN_ASSISTANT)); + } + + @Test + public void hasPosition_positionWithSameIdentityFieldsInHrManager_returnsTrue() { + hrManager.addPosition(ADMIN_ASSISTANT); + Position editedAdminAssistant = new PositionBuilder(ADMIN_ASSISTANT).build(); + assertTrue(hrManager.hasPosition(editedAdminAssistant)); + } + + @Test + public void getPositionList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> hrManager.getPositionList().remove(0)); + } + + @Test + public void deletePositionFromPerson() { + HrManager testManager = new HrManager(); + Position hrManager = new Position(new Title("HR Manager")); + List<Person> persons = testManager.getPersonList(); + testManager.addPerson(new PersonBuilder().withName("A").withEmail("A@yahoo.com").build()); + testManager.addPerson(new PersonBuilder().withName("B").withEmail("B@gmail.com").build()); + testManager.addPerson(new PersonBuilder().withName("C").withEmail("C@outlook.com").build()); + + // All persons applied for HrManager + for (Person p : persons) { + assertTrue(p.appliedForPosition(hrManager)); + } + + testManager.deletePositionFromPerson(hrManager); + + // All persons did not apply for HrManager + for (Person p : persons) { + assertFalse(p.appliedForPosition(hrManager)); + } + } + + @Test + public void hasInterview_nullInterview_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> hrManager.hasInterview(null)); + } + + @Test + public void hasInterview_interviewNotInHrManager_returnsFalse() { + assertFalse(hrManager.hasInterview(ASSISTANT_INTERVIEW)); + } + + @Test + public void hasInterview_interviewInHrManager_returnsTrue() { + hrManager.addInterview(ASSISTANT_INTERVIEW); + assertTrue(hrManager.hasInterview(ASSISTANT_INTERVIEW)); + } + + @Test + public void hasInterview_interviewWithSameIdentityFieldsInHrManager_returnsTrue() { + hrManager.addInterview(ASSISTANT_INTERVIEW); + Interview editedAssistantInterview = new InterviewBuilder(ASSISTANT_INTERVIEW).build(); + assertTrue(hrManager.hasInterview(editedAssistantInterview)); + } + + @Test + public void getInterviewList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> hrManager.getInterviewList().remove(0)); + } + + /** + * A stub ReadOnlyHrManager whose persons list can violate interface constraints. + */ + private static class HrManagerStub implements ReadOnlyHrManager { + private final ObservableList<Person> persons = FXCollections.observableArrayList(); + private final ObservableList<Position> positions = FXCollections.observableArrayList(); + private final ObservableList<Interview> interviews = FXCollections.observableArrayList(); + + private interface CollectionPersonRef extends Supplier<Collection<Person>> { + } + + HrManagerStub(CollectionPersonRef persons) { + this.persons.setAll(persons.get()); + } + + private interface CollectionPositionRef extends Supplier<Collection<Position>> { + } + + HrManagerStub(CollectionPositionRef positions) { + this.positions.setAll(positions.get()); + } + + private interface CollectionInterviewRef extends Supplier<Collection<Interview>> { + } + + HrManagerStub(CollectionInterviewRef interviews) { + this.interviews.setAll(interviews.get()); + } + + public void setPositionsStub(Collection<Position> positions) { + this.positions.setAll(positions); + } + + @Override + public ObservableList<Person> getPersonList() { + return persons; + } + + @Override + public ObservableList<Position> getPositionList() { + return positions; + } + + @Override + public ObservableList<Interview> getInterviewList() { + return interviews; + } + } + +} diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 2cf1418d116..1b2197d5d4b 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -3,10 +3,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_INTERVIEWS; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_POSITIONS; import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalInterviews.ASSISTANT_INTERVIEW; +import static seedu.address.testutil.TypicalInterviews.BOOKKEEPER_INTERVIEW; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; +import static seedu.address.testutil.TypicalPositions.BOOKKEEPER; import java.nio.file.Path; import java.nio.file.Paths; @@ -15,8 +21,10 @@ import org.junit.jupiter.api.Test; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.testutil.AddressBookBuilder; +import seedu.address.model.interview.PositionTitleContainsKeywordsPredicate; +import seedu.address.model.person.FindCandidateCommandPredicate; +import seedu.address.model.position.TitleContainsKeywordsPredicate; +import seedu.address.testutil.HrManagerBuilder; public class ModelManagerTest { @@ -26,7 +34,7 @@ public class ModelManagerTest { public void constructor() { assertEquals(new UserPrefs(), modelManager.getUserPrefs()); assertEquals(new GuiSettings(), modelManager.getGuiSettings()); - assertEquals(new AddressBook(), new AddressBook(modelManager.getAddressBook())); + assertEquals(new HrManager(), new HrManager(modelManager.getHrManager())); } @Test @@ -37,14 +45,14 @@ public void setUserPrefs_nullUserPrefs_throwsNullPointerException() { @Test public void setUserPrefs_validUserPrefs_copiesUserPrefs() { UserPrefs userPrefs = new UserPrefs(); - userPrefs.setAddressBookFilePath(Paths.get("address/book/file/path")); + userPrefs.setHrManagerCandidatesFilePath(Paths.get("address/book/file/path")); userPrefs.setGuiSettings(new GuiSettings(1, 2, 3, 4)); modelManager.setUserPrefs(userPrefs); assertEquals(userPrefs, modelManager.getUserPrefs()); // Modifying userPrefs should not modify modelManager's userPrefs UserPrefs oldUserPrefs = new UserPrefs(userPrefs); - userPrefs.setAddressBookFilePath(Paths.get("new/address/book/file/path")); + userPrefs.setHrManagerCandidatesFilePath(Paths.get("new/address/book/file/path")); assertEquals(oldUserPrefs, modelManager.getUserPrefs()); } @@ -61,15 +69,39 @@ public void setGuiSettings_validGuiSettings_setsGuiSettings() { } @Test - public void setAddressBookFilePath_nullPath_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> modelManager.setAddressBookFilePath(null)); + public void setHrManagerCandidateFilePath_nullPath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.setHrManagerCandidatesFilePath(null)); } @Test - public void setAddressBookFilePath_validPath_setsAddressBookFilePath() { + public void setHrManagerCandidateFilePath_validPath_setsHrManagerCandidateFilePath() { Path path = Paths.get("address/book/file/path"); - modelManager.setAddressBookFilePath(path); - assertEquals(path, modelManager.getAddressBookFilePath()); + modelManager.setHrManagerCandidatesFilePath(path); + assertEquals(path, modelManager.getHrManagerCandidatesFilePath()); + } + + @Test + public void setHrManagerPositionsFilePath_nullPath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.setHrManagerPositionsFilePath(null)); + } + + @Test + public void setHrManagerPositionsFilePath_validPath_setsHrManagerPositionsFilePath() { + Path path = Paths.get("address/book/file/path"); + modelManager.setHrManagerPositionsFilePath(path); + assertEquals(path, modelManager.getHrManagerPositionsFilePath()); + } + + @Test + public void setHrManagerInterviewsFilePath_nullPath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.setHrManagerInterviewsFilePath(null)); + } + + @Test + public void setHrManagerInterviewsFilePath_validPath_setsHrManagerInterviewsFilePath() { + Path path = Paths.get("address/book/file/path"); + modelManager.setHrManagerInterviewsFilePath(path); + assertEquals(path, modelManager.getHrManagerInterviewsFilePath()); } @Test @@ -78,12 +110,12 @@ public void hasPerson_nullPerson_throwsNullPointerException() { } @Test - public void hasPerson_personNotInAddressBook_returnsFalse() { + public void hasPerson_personNotInHrManager_returnsFalse() { assertFalse(modelManager.hasPerson(ALICE)); } @Test - public void hasPerson_personInAddressBook_returnsTrue() { + public void hasPerson_personInHrManager_returnsTrue() { modelManager.addPerson(ALICE); assertTrue(modelManager.hasPerson(ALICE)); } @@ -93,15 +125,61 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0)); } + @Test + public void hasPosition_nullPosition_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.hasPosition(null)); + } + + @Test + public void hasPosition_positionNotInHrManager_returnsFalse() { + assertFalse(modelManager.hasPosition(ADMIN_ASSISTANT)); + } + + @Test + public void hasPosition_positionInHrManager_returnsTrue() { + modelManager.addPosition(ADMIN_ASSISTANT); + assertTrue(modelManager.hasPosition(ADMIN_ASSISTANT)); + } + + @Test + public void getFilteredPositionList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPositionList().remove(0)); + } + + @Test + public void hasInterview_nullInterview_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> modelManager.hasInterview(null)); + } + + @Test + public void hasInterview_interviewNotInHrManager_returnsFalse() { + assertFalse(modelManager.hasInterview(ASSISTANT_INTERVIEW)); + } + + @Test + public void hasInterview_interviewInHrManager_returnsTrue() { + modelManager.addInterview(ASSISTANT_INTERVIEW); + assertTrue(modelManager.hasInterview(ASSISTANT_INTERVIEW)); + } + + @Test + public void getFilteredInterviewList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredInterviewList().remove(0)); + } + @Test public void equals() { - AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); - AddressBook differentAddressBook = new AddressBook(); + HrManager hrManager = new HrManagerBuilder() + .withPerson(ALICE).withPerson(BENSON) + .withPosition(ADMIN_ASSISTANT).withPosition(BOOKKEEPER) + .withInterview(ASSISTANT_INTERVIEW).withInterview(BOOKKEEPER_INTERVIEW) + .build(); + HrManager differentHrManager = new HrManager(); UserPrefs userPrefs = new UserPrefs(); // same values -> returns true - modelManager = new ModelManager(addressBook, userPrefs); - ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs); + modelManager = new ModelManager(hrManager, userPrefs); + ModelManager modelManagerCopy = new ModelManager(hrManager, userPrefs); assertTrue(modelManager.equals(modelManagerCopy)); // same object -> returns true @@ -113,20 +191,30 @@ public void equals() { // different types -> returns false assertFalse(modelManager.equals(5)); - // different addressBook -> returns false - assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs))); + // different hrManager -> returns false + assertFalse(modelManager.equals(new ModelManager(differentHrManager, userPrefs))); // different filteredList -> returns false String[] keywords = ALICE.getName().fullName.split("\\s+"); - modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); - assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); + modelManager.updateFilteredPersonList(new FindCandidateCommandPredicate(Arrays.asList(keywords))); + assertFalse(modelManager.equals(new ModelManager(hrManager, userPrefs))); + + String[] keywordsTitle = ADMIN_ASSISTANT.getTitle().fullTitle.split("\\s+"); + modelManager.updateFilteredPositionList(new TitleContainsKeywordsPredicate(Arrays.asList(keywords))); + assertFalse(modelManager.equals(new ModelManager(hrManager, userPrefs))); + + String[] keywordsInterviewPositionTitle = ADMIN_ASSISTANT.getTitle().fullTitle.split("\\s+"); + modelManager.updateFilteredInterviewList(new PositionTitleContainsKeywordsPredicate(Arrays.asList(keywords))); + assertFalse(modelManager.equals(new ModelManager(hrManager, userPrefs))); // resets modelManager to initial state for upcoming tests modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + modelManager.updateFilteredPositionList(PREDICATE_SHOW_ALL_POSITIONS); + modelManager.updateFilteredInterviewList(PREDICATE_SHOW_ALL_INTERVIEWS); // different userPrefs -> returns false UserPrefs differentUserPrefs = new UserPrefs(); - differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath")); - assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs))); + differentUserPrefs.setHrManagerCandidatesFilePath(Paths.get("differentFilePath")); + assertFalse(modelManager.equals(new ModelManager(hrManager, differentUserPrefs))); } } diff --git a/src/test/java/seedu/address/model/UserPrefsTest.java b/src/test/java/seedu/address/model/UserPrefsTest.java index b1307a70d52..014cb3bbd36 100644 --- a/src/test/java/seedu/address/model/UserPrefsTest.java +++ b/src/test/java/seedu/address/model/UserPrefsTest.java @@ -1,5 +1,8 @@ package seedu.address.model; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; @@ -13,9 +16,29 @@ public void setGuiSettings_nullGuiSettings_throwsNullPointerException() { } @Test - public void setAddressBookFilePath_nullPath_throwsNullPointerException() { + public void setHrManagerCandidateFilePath_nullPath_throwsNullPointerException() { UserPrefs userPrefs = new UserPrefs(); - assertThrows(NullPointerException.class, () -> userPrefs.setAddressBookFilePath(null)); + assertThrows(NullPointerException.class, () -> userPrefs.setHrManagerCandidatesFilePath(null)); + } + + @Test + public void equals() { + UserPrefs userPrefs = new UserPrefs(); + + // same values -> returns true + UserPrefs anotherUserPrefs = new UserPrefs(); + assertTrue(userPrefs.equals(anotherUserPrefs)); + + // same object -> returns true + assertEquals(userPrefs, userPrefs); + assertTrue(anotherUserPrefs.equals(anotherUserPrefs)); + assertEquals(userPrefs.hashCode(), anotherUserPrefs.hashCode()); + + // null -> returns false + assertFalse(userPrefs.equals(null)); + + // different types -> returns false + assertFalse(userPrefs.equals(5)); } } diff --git a/src/test/java/seedu/address/model/interview/FindInterviewCommandPredicateTest.java b/src/test/java/seedu/address/model/interview/FindInterviewCommandPredicateTest.java new file mode 100644 index 00000000000..03b280d56b2 --- /dev/null +++ b/src/test/java/seedu/address/model/interview/FindInterviewCommandPredicateTest.java @@ -0,0 +1,141 @@ +package seedu.address.model.interview; + + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.TypicalPersons.CARL; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.person.Person; +import seedu.address.testutil.InterviewBuilder; + +public class FindInterviewCommandPredicateTest { + + @Test + public void equals() { + List<String> firstPredicateKeywordList = Collections.singletonList("first"); + List<String> secondPredicateKeywordList = Arrays.asList("first", "second"); + + FindInterviewCommandPredicate firstPredicate = new FindInterviewCommandPredicate( + firstPredicateKeywordList, + Arrays.asList(), + Arrays.asList(), + Arrays.asList(), + Arrays.asList() + ); + FindInterviewCommandPredicate secondPredicate = new FindInterviewCommandPredicate( + secondPredicateKeywordList, + Arrays.asList(), + Arrays.asList(), + Arrays.asList(), + Arrays.asList() + ); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + FindInterviewCommandPredicate firstPredicateCopy = new FindInterviewCommandPredicate(firstPredicateKeywordList, + Arrays.asList(), + Arrays.asList(), + Arrays.asList(), + Arrays.asList()); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different interview -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_candiatesContainsKeywords_returnsTrue() { + // One keyword + FindInterviewCommandPredicate predicate = new FindInterviewCommandPredicate( + Collections.singletonList("Alice"), + Arrays.asList(), + Arrays.asList(), + Arrays.asList(), + Arrays.asList() + ); + assertTrue(predicate.test(new InterviewBuilder().withCandidates(new HashSet<Person>(Arrays.asList(ALICE))) + .build())); + + // Multiple keywords + predicate = new FindInterviewCommandPredicate( + Arrays.asList("Alice", "Benson"), + Arrays.asList(), + Arrays.asList(), + Arrays.asList(), + Arrays.asList() + ); + assertTrue(predicate.test(new InterviewBuilder().withCandidates(new HashSet<Person>(Arrays.asList(ALICE, + BENSON))).build())); + + // Only one matching keyword + predicate = new FindInterviewCommandPredicate( + Arrays.asList("Alice", "Benson"), + Arrays.asList(), + Arrays.asList(), + Arrays.asList(), + Arrays.asList() + ); + assertTrue(predicate.test(new InterviewBuilder().withCandidates(new HashSet<Person>(Arrays.asList(ALICE, + CARL))).build())); + + // Mixed-case keywords + predicate = new FindInterviewCommandPredicate(Arrays.asList("aLiCe", "BENSON"), + Arrays.asList(), + Arrays.asList(), + Arrays.asList(), + Arrays.asList()); + assertTrue(predicate.test(new InterviewBuilder().withCandidates(new HashSet<Person>(Arrays.asList(ALICE, + BENSON))).build())); + } + + @Test + public void test_candidatesDoesNotContainKeywords_returnsFalse() { + // Zero keywords + FindInterviewCommandPredicate predicate = new FindInterviewCommandPredicate(); + + // Non-matching keyword + predicate = new FindInterviewCommandPredicate( + Arrays.asList("Carl"), + Arrays.asList(), + Arrays.asList(), + Arrays.asList(), + Arrays.asList() + ); + assertFalse(predicate.test(new InterviewBuilder().withCandidates(new HashSet<Person>(Arrays.asList(ALICE, + BENSON))).build())); + + // Keywords match candidate, time, date but not status + predicate = new FindInterviewCommandPredicate(Arrays.asList("Carl"), + Arrays.asList("2021-09-21"), + Arrays.asList("Completed"), + Arrays.asList(), + Arrays.asList(LocalTime.of(12, 0))); + assertFalse(predicate.test(new InterviewBuilder() + .withCandidates(new HashSet<Person>(Arrays.asList(CARL))) + .withDate(LocalDate.of(2021, 9, 21)) + .withStartTime(LocalTime.of(12, 0)) + .withStatus(Interview.InterviewStatus.PENDING) + .build())); + + } + +} diff --git a/src/test/java/seedu/address/model/interview/InterviewTest.java b/src/test/java/seedu/address/model/interview/InterviewTest.java new file mode 100644 index 00000000000..fdffaf59d71 --- /dev/null +++ b/src/test/java/seedu/address/model/interview/InterviewTest.java @@ -0,0 +1,144 @@ +package seedu.address.model.interview; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.interview.CommandTestUtil.VALID_CANDIDATES_SET; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DURATION; +import static seedu.address.logic.interview.CommandTestUtil.VALID_DURATION_OTHER_DURATION; +import static seedu.address.logic.interview.CommandTestUtil.VALID_LOCAL_DATE_OTHER_DATE; +import static seedu.address.logic.interview.CommandTestUtil.VALID_START_TIME; +import static seedu.address.logic.interview.CommandTestUtil.VALID_START_TIME_OTHER_START_TIME; +import static seedu.address.logic.interview.CommandTestUtil.VALID_STATUS_COMPLETED; +import static seedu.address.testutil.TypicalInterviews.ASSISTANT_INTERVIEW; +import static seedu.address.testutil.TypicalInterviews.BOOKKEEPER_INTERVIEW; +import static seedu.address.testutil.TypicalPersons.getTypicalPersons; +import static seedu.address.testutil.TypicalPositions.BOOKKEEPER; + +import java.time.Duration; +import java.util.HashSet; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.InterviewBuilder; + +public class InterviewTest { + + @Test + public void isSameInterview() { + // same object -> returns true + assertTrue(ASSISTANT_INTERVIEW.isSameInterview(ASSISTANT_INTERVIEW)); + + // null -> returns false + assertFalse(ASSISTANT_INTERVIEW.isSameInterview(null)); + + // same name, different candidates -> returns true + Interview editedAssistanceInterview = new InterviewBuilder(ASSISTANT_INTERVIEW).build(); + assertTrue(ASSISTANT_INTERVIEW.isSameInterview(editedAssistanceInterview)); + + // different position, all other attributes same -> returns false + editedAssistanceInterview = new InterviewBuilder(ASSISTANT_INTERVIEW).withPosition(BOOKKEEPER).build(); + assertFalse(ASSISTANT_INTERVIEW.isSameInterview(editedAssistanceInterview)); + + // different candidate, all other attributes same -> returns true + editedAssistanceInterview = new InterviewBuilder(ASSISTANT_INTERVIEW) + .withCandidates(VALID_CANDIDATES_SET).build(); + assertTrue(ASSISTANT_INTERVIEW.isSameInterview(editedAssistanceInterview)); + + // different start time, all other attributes same -> returns false + editedAssistanceInterview = new InterviewBuilder(ASSISTANT_INTERVIEW).withStartTime(VALID_START_TIME).build(); + assertFalse(ASSISTANT_INTERVIEW.isSameInterview(editedAssistanceInterview)); + + // different duration, all other attributes same -> returns false + editedAssistanceInterview = new InterviewBuilder(ASSISTANT_INTERVIEW).withDuration(VALID_DURATION).build(); + assertFalse(ASSISTANT_INTERVIEW.isSameInterview(editedAssistanceInterview)); + + // different status, all other attribute same -> return true + editedAssistanceInterview = new InterviewBuilder(ASSISTANT_INTERVIEW) + .withStatus(VALID_STATUS_COMPLETED).build(); + assertTrue(ASSISTANT_INTERVIEW.isSameInterview(editedAssistanceInterview)); + + } + + @Test + public void equals() { + // same values -> returns true + Interview assistantInterviewCopy = new InterviewBuilder(ASSISTANT_INTERVIEW).build(); + assertTrue(ASSISTANT_INTERVIEW.equals(assistantInterviewCopy)); + + // same object -> returns true + assertTrue(ASSISTANT_INTERVIEW.equals(ASSISTANT_INTERVIEW)); + + // null -> returns false + assertFalse(assistantInterviewCopy.equals(null)); + + // different type -> returns false + assertFalse(assistantInterviewCopy.equals(5)); + + // different interview -> returns false + assertFalse(ASSISTANT_INTERVIEW.equals(BOOKKEEPER_INTERVIEW)); + + // different position -> returns false + Interview editedAssistantInterview = new InterviewBuilder(ASSISTANT_INTERVIEW).withPosition(BOOKKEEPER).build(); + assertFalse(ASSISTANT_INTERVIEW.equals(editedAssistantInterview)); + + // different candidate -> returns false + editedAssistantInterview = new InterviewBuilder(ASSISTANT_INTERVIEW) + .withCandidates(new HashSet<>(getTypicalPersons())).build(); + assertFalse(ASSISTANT_INTERVIEW.equals(editedAssistantInterview)); + + // different start time -> return false + editedAssistantInterview = new InterviewBuilder(ASSISTANT_INTERVIEW) + .withStartTime(VALID_START_TIME_OTHER_START_TIME).build(); + assertFalse(ASSISTANT_INTERVIEW.equals(editedAssistantInterview)); + + // different date -> return false + editedAssistantInterview = new InterviewBuilder(ASSISTANT_INTERVIEW) + .withDate(VALID_LOCAL_DATE_OTHER_DATE).build(); + assertFalse(ASSISTANT_INTERVIEW.equals(editedAssistantInterview)); + + // different duration -> return false + editedAssistantInterview = new InterviewBuilder(ASSISTANT_INTERVIEW) + .withDuration(VALID_DURATION_OTHER_DURATION).build(); + assertFalse(ASSISTANT_INTERVIEW.equals(editedAssistantInterview)); + + // different status -> return false + editedAssistantInterview = new InterviewBuilder(ASSISTANT_INTERVIEW).withStatus(VALID_STATUS_COMPLETED).build(); + assertFalse(ASSISTANT_INTERVIEW.equals(editedAssistantInterview)); + + //toString returns same value + assertEquals(ASSISTANT_INTERVIEW.toString(), assistantInterviewCopy.toString()); + } + + @Test + public void durationFormatting() { + Interview interview = new InterviewBuilder().withDuration(VALID_DURATION).build(); + //only in hours + assertEquals(interview.getDurationInFormattedString(), "180"); + + //only in minutes + interview = new InterviewBuilder().withDuration(Duration.ofMinutes(20)).build(); + assertEquals(interview.getDurationInFormattedString(), "20"); + + //hours and minutes + interview = new InterviewBuilder().withDuration(Duration.ofMinutes(125)).build(); + assertEquals(interview.getDurationInFormattedString(), "125"); + } + + @Test + public void interview_nullStatus_throwsException() { + Interview interview = new InterviewBuilder().build(); + assertThrows(NullPointerException.class, () -> interview.setStatus(null)); + } + + @Test + public void setStatus_success() { + Interview interview = new InterviewBuilder().build(); + interview.setStatus(Interview.InterviewStatus.COMPLETED); + assertEquals(interview.getStatus(), Interview.InterviewStatus.COMPLETED); + + interview.setStatus(Interview.InterviewStatus.PENDING); + assertEquals(interview.getStatus(), Interview.InterviewStatus.PENDING); + } +} diff --git a/src/test/java/seedu/address/model/interview/PositionTitleContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/interview/PositionTitleContainsKeywordsPredicateTest.java new file mode 100644 index 00000000000..673fcdde628 --- /dev/null +++ b/src/test/java/seedu/address/model/interview/PositionTitleContainsKeywordsPredicateTest.java @@ -0,0 +1,79 @@ +package seedu.address.model.interview; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.InterviewBuilder; + +public class PositionTitleContainsKeywordsPredicateTest { + + @Test + public void equals() { + List<String> firstPredicateKeywordList = Collections.singletonList("first"); + List<String> secondPredicateKeywordList = Arrays.asList("first", "second"); + + PositionTitleContainsKeywordsPredicate firstPredicate = + new PositionTitleContainsKeywordsPredicate(firstPredicateKeywordList); + PositionTitleContainsKeywordsPredicate secondPredicate = + new PositionTitleContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertEquals(firstPredicate, firstPredicate); + + // same values -> returns true + PositionTitleContainsKeywordsPredicate firstPredicateCopy = + new PositionTitleContainsKeywordsPredicate(firstPredicateKeywordList); + assertEquals(firstPredicate, firstPredicateCopy); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different person -> returns false + assertNotEquals(firstPredicate, secondPredicate); + } + + @Test + public void test_positionTitleContainsKeywords_returnsTrue() { + // One keyword + PositionTitleContainsKeywordsPredicate predicate = + new PositionTitleContainsKeywordsPredicate(Collections.singletonList("Administrative")); + assertTrue(predicate.test(new InterviewBuilder().withPosition(ADMIN_ASSISTANT).build())); + + // Multiple keywords + predicate = new PositionTitleContainsKeywordsPredicate(Arrays.asList("Administrative", "Assistant")); + assertTrue(predicate.test(new InterviewBuilder().withPosition(ADMIN_ASSISTANT).build())); + + // Only one matching keyword + predicate = new PositionTitleContainsKeywordsPredicate(Arrays.asList("Assistant", "Bookkeeper")); + assertTrue(predicate.test(new InterviewBuilder().withPosition(ADMIN_ASSISTANT).build())); + + // Mixed-case keywords + predicate = new PositionTitleContainsKeywordsPredicate(Arrays.asList("AdminIsTrAtiVe", "asSIsTaNt")); + assertTrue(predicate.test(new InterviewBuilder().withPosition(ADMIN_ASSISTANT).build())); + } + + @Test + public void test_positionTitleDoesNotContainKeywords_returnsFalse() { + // Zero keywords + PositionTitleContainsKeywordsPredicate predicate = + new PositionTitleContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new InterviewBuilder().withPosition(ADMIN_ASSISTANT).build())); + + // Non-matching keyword + predicate = new PositionTitleContainsKeywordsPredicate(Collections.singletonList("Bookkeeper")); + assertFalse(predicate.test(new InterviewBuilder().withPosition(ADMIN_ASSISTANT).build())); + + } +} diff --git a/src/test/java/seedu/address/model/interview/TupleTest.java b/src/test/java/seedu/address/model/interview/TupleTest.java new file mode 100644 index 00000000000..2312299e477 --- /dev/null +++ b/src/test/java/seedu/address/model/interview/TupleTest.java @@ -0,0 +1,43 @@ +package seedu.address.model.interview; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.time.LocalDate; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.index.Index; +import seedu.address.testutil.InterviewBuilder; + +class TupleTest { + + @Test + public void equals() { + Interview interview = new InterviewBuilder().build(); + Index index = Index.fromZeroBased(1); + + Interview otherInterview = new InterviewBuilder().withDate(LocalDate.now()).build(); + Index otherIndex = Index.fromZeroBased(2); + + Tuple<Interview, Index> tuple = new Tuple<>(interview, index); + Tuple<Interview, Index> otherTuple = new Tuple<>(otherInterview, otherIndex); + Tuple<Interview, Index> tupleCopy = new Tuple<>(interview, index); + + //same Object returns true + assertEquals(tuple, tuple); + assertEquals(otherTuple, otherTuple); + + //same values returns true + assertEquals(tuple, tupleCopy); + + //different value returns false + assertNotEquals(tuple, otherTuple); + + //incompatible types returns false + assertNotEquals(tuple, new Tuple<>(index, otherIndex)); + + //different type returns false + assertNotEquals(tuple, 6); + } +} diff --git a/src/test/java/seedu/address/model/interview/UniqueInterviewListTest.java b/src/test/java/seedu/address/model/interview/UniqueInterviewListTest.java new file mode 100644 index 00000000000..031a251544a --- /dev/null +++ b/src/test/java/seedu/address/model/interview/UniqueInterviewListTest.java @@ -0,0 +1,175 @@ +package seedu.address.model.interview; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalInterviews.ASSISTANT_INTERVIEW; +import static seedu.address.testutil.TypicalInterviews.BOOKKEEPER_INTERVIEW; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.interview.exceptions.DuplicateInterviewException; +import seedu.address.model.interview.exceptions.InterviewNotFoundException; +import seedu.address.testutil.InterviewBuilder; + +public class UniqueInterviewListTest { + + private final UniqueInterviewList uniqueInterviewList = new UniqueInterviewList(); + + @Test + public void contains_nullInterview_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueInterviewList.contains(null)); + } + + @Test + public void contains_interviewNotInList_returnsFalse() { + assertFalse(uniqueInterviewList.contains(ASSISTANT_INTERVIEW)); + } + + @Test + public void contains_interviewInList_returnsTrue() { + uniqueInterviewList.add(ASSISTANT_INTERVIEW); + assertTrue(uniqueInterviewList.contains(ASSISTANT_INTERVIEW)); + } + + @Test + public void contains_interviewWithSameIdentityFieldsInList_returnsTrue() { + uniqueInterviewList.add(ASSISTANT_INTERVIEW); + Interview editedAssistantInterview = new InterviewBuilder(ASSISTANT_INTERVIEW) + .withPosition(ADMIN_ASSISTANT) + .build(); + assertTrue(uniqueInterviewList.contains(editedAssistantInterview)); + } + + @Test + public void add_nullInterview_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueInterviewList.add(null)); + } + + @Test + public void add_duplicateInterview_throwsDuplicatePositionException() { + uniqueInterviewList.add(ASSISTANT_INTERVIEW); + assertThrows(DuplicateInterviewException.class, () -> uniqueInterviewList.add(ASSISTANT_INTERVIEW)); + } + + @Test + public void setInterview_nullTargetInterview_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> + uniqueInterviewList.setInterview(null, ASSISTANT_INTERVIEW)); + } + + @Test + public void setInterview_nullEditedInterview_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> + uniqueInterviewList.setInterview(ASSISTANT_INTERVIEW, null)); + } + + @Test + public void setInterview_targetInterviewNotInList_throwsInterviewNotFoundException() { + assertThrows(InterviewNotFoundException.class, () -> + uniqueInterviewList.setInterview(ASSISTANT_INTERVIEW, ASSISTANT_INTERVIEW)); + } + + @Test + public void setInterview_editedInterviewIsSameInterview_success() { + uniqueInterviewList.add(ASSISTANT_INTERVIEW); + uniqueInterviewList.setInterview(ASSISTANT_INTERVIEW, ASSISTANT_INTERVIEW); + UniqueInterviewList expectedUniqueInterviewList = new UniqueInterviewList(); + expectedUniqueInterviewList.add(ASSISTANT_INTERVIEW); + assertEquals(expectedUniqueInterviewList, uniqueInterviewList); + } + + @Test + public void setInterview_editedInterviewHasSameIdentity_success() { + uniqueInterviewList.add(ASSISTANT_INTERVIEW); + Interview editedAssistantInterview = new InterviewBuilder(ASSISTANT_INTERVIEW).build(); + uniqueInterviewList.setInterview(ASSISTANT_INTERVIEW, editedAssistantInterview); + UniqueInterviewList expectedUniqueInterviewList = new UniqueInterviewList(); + expectedUniqueInterviewList.add(editedAssistantInterview); + assertEquals(expectedUniqueInterviewList, uniqueInterviewList); + } + + @Test + public void setInterview_editedInterviewHasDifferentIdentity_success() { + uniqueInterviewList.add(ASSISTANT_INTERVIEW); + uniqueInterviewList.setInterview(ASSISTANT_INTERVIEW, BOOKKEEPER_INTERVIEW); + UniqueInterviewList expectedUniqueInterviewList = new UniqueInterviewList(); + expectedUniqueInterviewList.add(BOOKKEEPER_INTERVIEW); + assertEquals(expectedUniqueInterviewList, uniqueInterviewList); + } + + @Test + public void setInterview_editedInterviewHasNonUniqueIdentity_throwsDuplicateInterviewException() { + uniqueInterviewList.add(ASSISTANT_INTERVIEW); + uniqueInterviewList.add(BOOKKEEPER_INTERVIEW); + assertThrows(DuplicateInterviewException.class, () -> + uniqueInterviewList.setInterview(ASSISTANT_INTERVIEW, BOOKKEEPER_INTERVIEW)); + } + + @Test + public void remove_nullInterview_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueInterviewList.remove(null)); + } + + @Test + public void remove_interviewDoesNotExist_throwsInterviewNotFoundException() { + assertThrows(InterviewNotFoundException.class, () -> uniqueInterviewList.remove(ASSISTANT_INTERVIEW)); + } + + @Test + public void remove_existingInterview_removesInterview() { + uniqueInterviewList.add(ASSISTANT_INTERVIEW); + uniqueInterviewList.remove(ASSISTANT_INTERVIEW); + UniqueInterviewList expectedUniqueInterviewList = new UniqueInterviewList(); + assertEquals(expectedUniqueInterviewList, uniqueInterviewList); + } + + @Test + public void setInterviews_nullUniqueInterviewList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueInterviewList.setInterviews((UniqueInterviewList) null)); + } + + @Test + public void setInterviews_uniqueInterviewList_replacesOwnListWithProvidedUniqueInterviewList() { + uniqueInterviewList.add(ASSISTANT_INTERVIEW); + UniqueInterviewList expectedUniqueInterviewList = new UniqueInterviewList(); + expectedUniqueInterviewList.add(BOOKKEEPER_INTERVIEW); + uniqueInterviewList.setInterviews(expectedUniqueInterviewList); + assertEquals(expectedUniqueInterviewList, uniqueInterviewList); + } + + @Test + public void setInterviews_nullList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniqueInterviewList.setInterviews((List<Interview>) null)); + } + + @Test + public void setInterviews_list_replacesOwnListWithProvidedList() { + uniqueInterviewList.add(ASSISTANT_INTERVIEW); + List<Interview> interviewList = Collections.singletonList(BOOKKEEPER_INTERVIEW); + uniqueInterviewList.setInterviews(interviewList); + UniqueInterviewList expectedUniqueInterviewList = new UniqueInterviewList(); + expectedUniqueInterviewList.add(BOOKKEEPER_INTERVIEW); + assertEquals(expectedUniqueInterviewList, uniqueInterviewList); + } + + @Test + public void setInterviews_listWithDuplicateInterviews_throwsDuplicateInterviewException() { + List<Interview> listWithDuplicateInterviews = Arrays.asList(ASSISTANT_INTERVIEW, ASSISTANT_INTERVIEW); + assertThrows(DuplicateInterviewException.class, () -> + uniqueInterviewList.setInterviews(listWithDuplicateInterviews)); + } + + @Test + public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> + uniqueInterviewList.asUnmodifiableObservableList().remove(0)); + } +} + diff --git a/src/test/java/seedu/address/model/person/FindCandidateCommandPredicateTest.java b/src/test/java/seedu/address/model/person/FindCandidateCommandPredicateTest.java new file mode 100644 index 00000000000..1c8af3772c8 --- /dev/null +++ b/src/test/java/seedu/address/model/person/FindCandidateCommandPredicateTest.java @@ -0,0 +1,208 @@ +package seedu.address.model.person; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.PersonBuilder; + +public class FindCandidateCommandPredicateTest { + + @Test + public void equals() { + List<String> firstPredicateKeywordList = Collections.singletonList("first"); + List<String> secondPredicateKeywordList = Arrays.asList("first", "second"); + + FindCandidateCommandPredicate firstPredicate = new FindCandidateCommandPredicate(firstPredicateKeywordList); + FindCandidateCommandPredicate secondPredicate = new FindCandidateCommandPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + FindCandidateCommandPredicate firstPredicateCopy = new FindCandidateCommandPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different person -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_nameContainsKeywords_returnsTrue() { + // One keyword + FindCandidateCommandPredicate predicate = new FindCandidateCommandPredicate(Collections.singletonList("Alice")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + // Multiple keywords + predicate = new FindCandidateCommandPredicate(Arrays.asList("Alice", "Bob")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + // Only one matching keyword + predicate = new FindCandidateCommandPredicate(Arrays.asList("Bob", "Carol")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build())); + + // Mixed-case keywords + predicate = new FindCandidateCommandPredicate(Arrays.asList("aLIce", "bOB")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + } + + @Test + public void test_nameDoesNotContainKeywords_returnsFalse() { + // Zero keywords + FindCandidateCommandPredicate predicate = new FindCandidateCommandPredicate(); + + // Non-matching keyword + predicate = new FindCandidateCommandPredicate(Arrays.asList("Carol")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + // Keywords match phone, email and address, but does not match name + predicate = new FindCandidateCommandPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") + .withEmail("alice@email.com").withAddress("Main Street").build())); + } + + @Test + public void test_phoneContainsKeywords() { + //valid number + FindCandidateCommandPredicate predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), Arrays.asList(ALICE.getPhone().value), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + assertTrue(predicate.test(new PersonBuilder().withPhone(ALICE.getPhone().value).build())); + + //invalid input non-existent number + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), Arrays.asList("12345678"), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + assertFalse(predicate.test(new PersonBuilder().withPhone(ALICE.getPhone().value).build())); + + //invalid input not all numbers + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), Arrays.asList("1a345678"), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + assertFalse(predicate.test(new PersonBuilder().withPhone(ALICE.getPhone().value).build())); + } + + @Test + public void test_emailContainsKeywords() { + //valid email + FindCandidateCommandPredicate predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), Arrays.asList(ALICE.getEmail().value), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + assertTrue(predicate.test(new PersonBuilder().withEmail(ALICE.getEmail().value).build())); + + //invalid input non-existent email + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), Arrays.asList("Alice@gmail.com"), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + assertFalse(predicate.test(new PersonBuilder().withEmail(ALICE.getEmail().value).build())); + + //invalid input not an email + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), Arrays.asList("blah"), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + assertFalse(predicate.test(new PersonBuilder().withEmail(ALICE.getEmail().value).build())); + } + + @Test + public void test_addressContainsKeywords() { + //valid address 1 keyword + FindCandidateCommandPredicate predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + Arrays.asList("jurong"), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + assertTrue(predicate.test(new PersonBuilder().withAddress(ALICE.getAddress().value).build())); + + //valid address 2 keywords and mixed case + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + Arrays.asList("JuRong", "WeSt"), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + assertTrue(predicate.test(new PersonBuilder().withAddress(ALICE.getAddress().value).build())); + + + //invalid input non-existent address + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + Arrays.asList("washington"), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + assertFalse(predicate.test(new PersonBuilder().withAddress(ALICE.getAddress().value).build())); + + //invalid input not an address + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + Arrays.asList(" ."), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + assertFalse(predicate.test(new PersonBuilder().withAddress(ALICE.getAddress().value).build())); + } + + @Test + public void test_tagsContainsKeywords() { + //valid tag + FindCandidateCommandPredicate predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), Arrays.asList(ALICE.getTagsString()), new ArrayList<>(), new ArrayList<>()); + assertTrue(predicate.test(new PersonBuilder().withTags(ALICE.getTagsString()).build())); + + //invalid input non-existent tag + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), Arrays.asList("bestfriend"), new ArrayList<>(), new ArrayList<>()); + assertFalse(predicate.test(new PersonBuilder().withTags(ALICE.getTagsString()).build())); + } + + @Test + public void test_statusContainsKeywords() { + //valid status + FindCandidateCommandPredicate predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), new ArrayList<>(), Arrays.asList(ALICE.getStatus().toString()), new ArrayList<>()); + assertTrue(predicate.test(new PersonBuilder().withStatus(ALICE.getStatus().toString()).build())); + + //valid status non-matching + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), new ArrayList<>(), Arrays.asList(Status.NONE.toString()), new ArrayList<>()); + assertFalse(predicate.test(new PersonBuilder().withStatus(ALICE.getStatus().toString()).build())); + + //invalid status not a status + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), new ArrayList<>(), Arrays.asList("withdrew"), new ArrayList<>()); + assertFalse(predicate.test(new PersonBuilder().withStatus(ALICE.getStatus().toString()).build())); + } + + @Test + public void test_positionContainsKeywords() { + //valid position + FindCandidateCommandPredicate predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), Arrays.asList( + BENSON.getPositionsString().split(" "))); + assertTrue(predicate.test(new PersonBuilder().withPositions(BENSON.getPositionsString()).build())); + + + //valid position not applied + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), Arrays.asList( + ADMIN_ASSISTANT.getTitle().fullTitle.split(" "))); + assertFalse(predicate.test(new PersonBuilder().withPositions(BENSON.getPositionsString()).build())); + + //invalid position + predicate = new FindCandidateCommandPredicate( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), Arrays.asList("HR-Manager")); + assertFalse(predicate.test(new PersonBuilder().withPositions(BENSON.getPositionsString()).build())); + } +} diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java deleted file mode 100644 index f136664e017..00000000000 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import seedu.address.testutil.PersonBuilder; - -public class NameContainsKeywordsPredicateTest { - - @Test - public void equals() { - List<String> firstPredicateKeywordList = Collections.singletonList("first"); - List<String> secondPredicateKeywordList = Arrays.asList("first", "second"); - - NameContainsKeywordsPredicate firstPredicate = new NameContainsKeywordsPredicate(firstPredicateKeywordList); - NameContainsKeywordsPredicate secondPredicate = new NameContainsKeywordsPredicate(secondPredicateKeywordList); - - // same object -> returns true - assertTrue(firstPredicate.equals(firstPredicate)); - - // same values -> returns true - NameContainsKeywordsPredicate firstPredicateCopy = new NameContainsKeywordsPredicate(firstPredicateKeywordList); - assertTrue(firstPredicate.equals(firstPredicateCopy)); - - // different types -> returns false - assertFalse(firstPredicate.equals(1)); - - // null -> returns false - assertFalse(firstPredicate.equals(null)); - - // different person -> returns false - assertFalse(firstPredicate.equals(secondPredicate)); - } - - @Test - public void test_nameContainsKeywords_returnsTrue() { - // One keyword - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.singletonList("Alice")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - - // Multiple keywords - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - - // Only one matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Bob", "Carol")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build())); - - // Mixed-case keywords - predicate = new NameContainsKeywordsPredicate(Arrays.asList("aLIce", "bOB")); - assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - } - - @Test - public void test_nameDoesNotContainKeywords_returnsFalse() { - // Zero keywords - NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.emptyList()); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); - - // Non-matching keyword - predicate = new NameContainsKeywordsPredicate(Arrays.asList("Carol")); - assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - - // Keywords match phone, email and address, but does not match name - predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") - .withEmail("alice@email.com").withAddress("Main Street").build())); - } -} diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java index b29c097cfd4..b57dce0d28c 100644 --- a/src/test/java/seedu/address/model/person/PersonTest.java +++ b/src/test/java/seedu/address/model/person/PersonTest.java @@ -1,20 +1,27 @@ package seedu.address.model.person; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BOB; +import java.util.HashSet; +import java.util.Set; + import org.junit.jupiter.api.Test; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; import seedu.address.testutil.PersonBuilder; + public class PersonTest { @Test @@ -31,23 +38,31 @@ public void isSamePerson() { // null -> returns false assertFalse(ALICE.isSamePerson(null)); - // same name, all other attributes different -> returns true - Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) + // same name, same phone number, same email, all other attributes different -> returns true + Person editedAlice = new PersonBuilder(ALICE) .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build(); assertTrue(ALICE.isSamePerson(editedAlice)); - // different name, all other attributes same -> returns false + // different name, all other attributes same -> same email -> returns true editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build(); + assertTrue(ALICE.isSamePerson(editedAlice)); + + //different phone, all other attributes same -> same email -> returns true + editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).build(); + assertTrue(ALICE.isSamePerson(editedAlice)); + + //different email, all other attributes same -> return false + editedAlice = new PersonBuilder(ALICE).withEmail(VALID_EMAIL_BOB).build(); assertFalse(ALICE.isSamePerson(editedAlice)); - // name differs in case, all other attributes same -> returns false + // name differs in case, all other attributes same -> same email -> returns true Person editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB.toLowerCase()).build(); - assertFalse(BOB.isSamePerson(editedBob)); + assertTrue(BOB.isSamePerson(editedBob)); - // name has trailing spaces, all other attributes same -> returns false + // name has trailing spaces, all other attributes same -> same email -> returns true String nameWithTrailingSpaces = VALID_NAME_BOB + " "; editedBob = new PersonBuilder(BOB).withName(nameWithTrailingSpaces).build(); - assertFalse(BOB.isSamePerson(editedBob)); + assertTrue(BOB.isSamePerson(editedBob)); } @Test @@ -87,5 +102,46 @@ public void equals() { // different tags -> returns false editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build(); assertFalse(ALICE.equals(editedAlice)); + + // different remarks -> returns false + editedAlice = new PersonBuilder(ALICE).withRemark("Hello").build(); + assertFalse(ALICE.equals(editedAlice)); + + // different positions -> returns false + editedAlice = new PersonBuilder(ALICE).withRemark("Admin Assistant").build(); + assertFalse(ALICE.equals(editedAlice)); + } + + @Test + public void appliedForPosition() { + //ALICE initially has position HR Manager + assertTrue(ALICE.appliedForPosition(new Position(new Title("Accountant")))); + + assertFalse(ALICE.appliedForPosition(new Position(new Title("Bookkeeper")))); + + //ALICE now has positions, Bookkeeper and Admin Assistant + Person editedAlice = new PersonBuilder(ALICE).withPositions("Bookkeeper", "Admin Assistant").build(); + + assertFalse(editedAlice.appliedForPosition(new Position(new Title("HR Manager")))); + assertTrue(editedAlice.appliedForPosition(new Position(new Title("Bookkeeper")))); + assertTrue(editedAlice.appliedForPosition(new Position(new Title("Admin Assistant")))); + + } + + @Test + public void deletePosition() { + Person editedAlice = new PersonBuilder(ALICE).withPositions("Bookkeeper", "Admin Assistant").build(); + + Position aa = new Position(new Title("Admin Assistant")); + Position bk = new Position(new Title("Bookkeeper")); + + editedAlice.deletePosition(aa); + //editedAlice now no longer has AA in its positions + assertFalse(editedAlice.appliedForPosition(aa)); + + Set<Position> positions = new HashSet<>(); + positions.add(bk); + //editedAlice now only has BK in its positions + assertEquals(positions, editedAlice.getPositions()); } } diff --git a/src/test/java/seedu/address/model/person/StatusTest.java b/src/test/java/seedu/address/model/person/StatusTest.java new file mode 100644 index 00000000000..15a3ae07404 --- /dev/null +++ b/src/test/java/seedu/address/model/person/StatusTest.java @@ -0,0 +1,20 @@ +package seedu.address.model.person; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class StatusTest { + + @Test + public void isCompleted() { + assertFalse(Status.NONE.isCompleted()); + assertFalse(Status.APPLIED.isCompleted()); + assertFalse(Status.SCHEDULED.isCompleted()); + assertFalse(Status.INTERVIEWED.isCompleted()); + assertFalse(Status.ACCEPTED.isCompleted()); + assertTrue(Status.REJECTED.isCompleted()); + assertTrue(Status.WITHDRAWN.isCompleted()); + } +} diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java index 1cc5fe9e0fe..34f2309ec46 100644 --- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java +++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java @@ -3,8 +3,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalPersons.ALICE; import static seedu.address.testutil.TypicalPersons.BOB; diff --git a/src/test/java/seedu/address/model/position/FindPositionCommandPredicateTest.java b/src/test/java/seedu/address/model/position/FindPositionCommandPredicateTest.java new file mode 100644 index 00000000000..6017a06c640 --- /dev/null +++ b/src/test/java/seedu/address/model/position/FindPositionCommandPredicateTest.java @@ -0,0 +1,104 @@ +package seedu.address.model.position; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.PositionBuilder; + + +public class FindPositionCommandPredicateTest { + + @Test + public void equals() { + List<String> firstPredicateKeywordList = Collections.singletonList("first"); + List<String> secondPredicateKeywordList = Arrays.asList("first", "second"); + + FindPositionCommandPredicate firstPredicate = new FindPositionCommandPredicate(firstPredicateKeywordList); + FindPositionCommandPredicate secondPredicate = new FindPositionCommandPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + FindPositionCommandPredicate firstPredicateCopy = new FindPositionCommandPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different position -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_nameContainsKeywords_returnsTrue() { + // One keyword + FindPositionCommandPredicate predicate = new FindPositionCommandPredicate(Collections.singletonList("Alice")); + assertTrue(predicate.test(new PositionBuilder().withTitle("Alice Bob").build())); + + // Multiple keywords + predicate = new FindPositionCommandPredicate(Arrays.asList("Alice", "Bob")); + assertTrue(predicate.test(new PositionBuilder().withTitle("Alice Bob").build())); + + // Only one matching keyword + predicate = new FindPositionCommandPredicate(Arrays.asList("Bob", "Carol")); + assertTrue(predicate.test(new PositionBuilder().withTitle("Alice Carol").build())); + + // Mixed-case keywords + predicate = new FindPositionCommandPredicate(Arrays.asList("aLIce", "bOB")); + assertTrue(predicate.test(new PositionBuilder().withTitle("Alice Bob").build())); + } + + @Test + public void test_nameDoesNotContainKeywords_returnsFalse() { + // Zero keywords + FindPositionCommandPredicate predicate = new FindPositionCommandPredicate(); + + // Non-matching keyword + predicate = new FindPositionCommandPredicate(Arrays.asList("Carol")); + assertFalse(predicate.test(new PositionBuilder().withTitle("Alice Bob").build())); + + // Keywords match phone, email and address, but does not match name + predicate = new FindPositionCommandPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new PositionBuilder().withTitle("Alice").withStatus(Position.PositionStatus.OPEN) + .build())); + } + + @Test + public void test_statusContainsKeywords_returnsTrue() { + //find closed + FindPositionCommandPredicate predicate = new FindPositionCommandPredicate( + new ArrayList<>(), Collections.singletonList("closed")); + assertTrue(predicate.test(new PositionBuilder() + .withStatus(Position.PositionStatus.CLOSED).build())); + + //find open + predicate = new FindPositionCommandPredicate( + new ArrayList<>(), Collections.singletonList("open")); + assertTrue(predicate.test(new PositionBuilder() + .withStatus(Position.PositionStatus.OPEN).build())); + } + + @Test + public void test_statusContainsInvalidKeywords_returnsFalse() { + FindPositionCommandPredicate predicate = new FindPositionCommandPredicate( + new ArrayList<>(), Collections.singletonList("close")); + assertFalse(predicate.test(new PositionBuilder() + .withStatus(Position.PositionStatus.CLOSED).build())); + + predicate = new FindPositionCommandPredicate( + new ArrayList<>(), Collections.singletonList("1")); + assertFalse(predicate.test(new PositionBuilder() + .withStatus(Position.PositionStatus.OPEN).build())); + } +} diff --git a/src/test/java/seedu/address/model/position/PositionTest.java b/src/test/java/seedu/address/model/position/PositionTest.java new file mode 100644 index 00000000000..9b84e218356 --- /dev/null +++ b/src/test/java/seedu/address/model/position/PositionTest.java @@ -0,0 +1,69 @@ +package seedu.address.model.position; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TITLE_BOOKKEEPER; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; +import static seedu.address.testutil.TypicalPositions.BOOKKEEPER; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.PositionBuilder; + +public class PositionTest { + + @Test + public void isSamePosition() { + // same object -> returns true + assertTrue(ADMIN_ASSISTANT.isSamePosition(ADMIN_ASSISTANT)); + + // null -> returns false + assertFalse(ADMIN_ASSISTANT.isSamePosition(null)); + + // same name, different candidates -> returns true + Position editedAdminAssistance = new PositionBuilder(ADMIN_ASSISTANT).build(); + assertTrue(ADMIN_ASSISTANT.isSamePosition(editedAdminAssistance)); + + // different name, all other attributes same -> returns false + editedAdminAssistance = new PositionBuilder(ADMIN_ASSISTANT).withTitle(VALID_TITLE_BOOKKEEPER).build(); + assertFalse(ADMIN_ASSISTANT.isSamePosition(editedAdminAssistance)); + + // name differs in case, all other attributes same -> returns false + Position editedBookkeeper = new PositionBuilder(BOOKKEEPER).withTitle(VALID_TITLE_BOOKKEEPER.toLowerCase()) + .build(); + assertTrue(BOOKKEEPER.isSamePosition(editedBookkeeper)); + + // name has trailing spaces, all other attributes same -> returns false + String nameWithTrailingSpaces = VALID_TITLE_BOOKKEEPER + " "; + editedBookkeeper = new PositionBuilder(BOOKKEEPER).withTitle(nameWithTrailingSpaces).build(); + assertFalse(BOOKKEEPER.isSamePosition(editedBookkeeper)); + + // Status is closed -> returns true + editedBookkeeper = new PositionBuilder(BOOKKEEPER).withStatus(Position.PositionStatus.CLOSED).build(); + assertTrue(editedBookkeeper.isClosed()); + + } + + @Test + public void equals() { + // same values -> returns true + Position adminAssistantCopy = new PositionBuilder(ADMIN_ASSISTANT).build(); + assertTrue(ADMIN_ASSISTANT.equals(adminAssistantCopy)); + + // same object -> returns true + assertTrue(ADMIN_ASSISTANT.equals(adminAssistantCopy)); + + // null -> returns false + assertFalse(adminAssistantCopy.equals(null)); + + // different type -> returns false + assertFalse(adminAssistantCopy.equals(5)); + + // different position -> returns false + assertFalse(ADMIN_ASSISTANT.equals(BOOKKEEPER)); + + // different title -> returns false + Position editedAdminAssistant = new PositionBuilder(ADMIN_ASSISTANT).withTitle(VALID_TITLE_BOOKKEEPER).build(); + assertFalse(ADMIN_ASSISTANT.equals(editedAdminAssistant)); + } +} diff --git a/src/test/java/seedu/address/model/position/TitleContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/position/TitleContainsKeywordsPredicateTest.java new file mode 100644 index 00000000000..ff38518ac27 --- /dev/null +++ b/src/test/java/seedu/address/model/position/TitleContainsKeywordsPredicateTest.java @@ -0,0 +1,76 @@ +package seedu.address.model.position; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.testutil.PositionBuilder; + +public class TitleContainsKeywordsPredicateTest { + + @Test + public void equals() { + List<String> firstPredicateKeywordList = Collections.singletonList("first"); + List<String> secondPredicateKeywordList = Arrays.asList("first", "second"); + + TitleContainsKeywordsPredicate firstPredicate = new TitleContainsKeywordsPredicate(firstPredicateKeywordList); + TitleContainsKeywordsPredicate secondPredicate = new TitleContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertEquals(firstPredicate, firstPredicate); + + // same values -> returns true + TitleContainsKeywordsPredicate firstPredicateCopy = + new TitleContainsKeywordsPredicate(firstPredicateKeywordList); + assertEquals(firstPredicate, firstPredicateCopy); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different person -> returns false + assertNotEquals(firstPredicate, secondPredicate); + } + + @Test + public void test_titleContainsKeywords_returnsTrue() { + // One keyword + TitleContainsKeywordsPredicate predicate = + new TitleContainsKeywordsPredicate(Collections.singletonList("Administrative")); + assertTrue(predicate.test(new PositionBuilder().withTitle("Administrative Assistant").build())); + + // Multiple keywords + predicate = new TitleContainsKeywordsPredicate(Arrays.asList("Administrative", "Assistant")); + assertTrue(predicate.test(new PositionBuilder().withTitle("Administrative Assistant").build())); + + // Only one matching keyword + predicate = new TitleContainsKeywordsPredicate(Arrays.asList("Assistant", "Bookkeeper")); + assertTrue(predicate.test(new PositionBuilder().withTitle("Administrative Assistant").build())); + + // Mixed-case keywords + predicate = new TitleContainsKeywordsPredicate(Arrays.asList("AdminIsTrAtiVe", "asSIsTaNt")); + assertTrue(predicate.test(new PositionBuilder().withTitle("Administrative Assistant").build())); + } + + @Test + public void test_positionTitleDoesNotContainKeywords_returnsFalse() { + // Zero keywords + TitleContainsKeywordsPredicate predicate = + new TitleContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new PositionBuilder().withTitle("Administrative Assistant").build())); + + // Non-matching keyword + predicate = new TitleContainsKeywordsPredicate(Collections.singletonList("Bookkeeper")); + assertFalse(predicate.test(new PositionBuilder().withTitle("Administrative Assistant").build())); + + } +} diff --git a/src/test/java/seedu/address/model/position/TitleTest.java b/src/test/java/seedu/address/model/position/TitleTest.java new file mode 100644 index 00000000000..99f4c62d4d4 --- /dev/null +++ b/src/test/java/seedu/address/model/position/TitleTest.java @@ -0,0 +1,69 @@ +package seedu.address.model.position; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +class TitleTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Title(null)); + } + + @Test + public void constructor_invalidTitle_throwsIllegalArgumentException() { + String invalidTitle = ""; + assertThrows(IllegalArgumentException.class, () -> new Title(invalidTitle)); + } + + @Test + public void isValidTitle() { + // null position name + assertThrows(NullPointerException.class, () -> Title.isValidTitle(null)); + } + + @Test + public void variousPositionNames() { + //invalid position names + assertFalse(Title.isValidTitle(("Administrative & Finance Assistant"))); + assertFalse(Title.isValidTitle(("Administrative Assistant #2"))); + assertFalse(Title.isValidTitle(("Administrative Assistant *"))); + assertFalse(Title.isValidTitle(("Administrative Assistant ^^"))); + assertFalse(Title.isValidTitle(("Acc-Manager"))); + assertFalse(Title.isValidTitle((""))); + assertFalse(Title.isValidTitle((" "))); + assertFalse(Title.isValidTitle(("Administrative Manager (temp)"))); + assertFalse(Title.isValidTitle(("'Fun!' project manager"))); + assertFalse(Title.isValidTitle(("data + admin clerk"))); + + //valid position names + assertTrue(Title.isValidTitle(("Administrative Assistant"))); + assertTrue(Title.isValidTitle(("Receptionist"))); + assertTrue(Title.isValidTitle(("Office Manager"))); + assertTrue(Title.isValidTitle(("Auditing Clerk"))); + assertTrue(Title.isValidTitle(("Bookkeeper"))); + assertTrue(Title.isValidTitle(("Account Executive Officer Assistant"))); + assertTrue(Title.isValidTitle(("Branch Manager"))); + assertTrue(Title.isValidTitle(("Business Manager"))); + assertTrue(Title.isValidTitle(("Quality Control Coordinator"))); + assertTrue(Title.isValidTitle(("Administrative Manager"))); + assertTrue(Title.isValidTitle(("Chief Executive Officer"))); + assertTrue(Title.isValidTitle(("Business Analyst"))); + assertTrue(Title.isValidTitle(("Risk Manager"))); + assertTrue(Title.isValidTitle(("Human Resources"))); + assertTrue(Title.isValidTitle(("Office Assistant"))); + assertTrue(Title.isValidTitle(("Secretary"))); + assertTrue(Title.isValidTitle(("Office Clerk"))); + assertTrue(Title.isValidTitle(("File Clerk"))); + assertTrue(Title.isValidTitle(("Account Collector"))); + assertTrue(Title.isValidTitle(("Administrative Specialist"))); + assertTrue(Title.isValidTitle(("Executive Assistant"))); + assertTrue(Title.isValidTitle(("Program Administrator"))); + assertTrue(Title.isValidTitle(("Program Manager"))); + assertTrue(Title.isValidTitle(("Administrative Analyst"))); + assertTrue(Title.isValidTitle(("Data Entry"))); + } +} diff --git a/src/test/java/seedu/address/model/position/UniquePositionListTest.java b/src/test/java/seedu/address/model/position/UniquePositionListTest.java new file mode 100644 index 00000000000..d10ff02710f --- /dev/null +++ b/src/test/java/seedu/address/model/position/UniquePositionListTest.java @@ -0,0 +1,173 @@ +package seedu.address.model.position; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TITLE_ADMIN_ASSISTANT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; +import static seedu.address.testutil.TypicalPositions.BOOKKEEPER; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.position.exceptions.DuplicatePositionException; +import seedu.address.model.position.exceptions.PositionNotFoundException; +import seedu.address.testutil.PositionBuilder; + +public class UniquePositionListTest { + + private final UniquePositionList uniquePositionList = new UniquePositionList(); + + @Test + public void contains_nullPosition_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniquePositionList.contains(null)); + } + + @Test + public void contains_positionNotInList_returnsFalse() { + assertFalse(uniquePositionList.contains(ADMIN_ASSISTANT)); + } + + @Test + public void contains_positionInList_returnsTrue() { + uniquePositionList.add(ADMIN_ASSISTANT); + assertTrue(uniquePositionList.contains(ADMIN_ASSISTANT)); + } + + @Test + public void contains_positionWithSameIdentityFieldsInList_returnsTrue() { + uniquePositionList.add(ADMIN_ASSISTANT); + Position editedAdminAssistant = new PositionBuilder(ADMIN_ASSISTANT) + .withTitle(VALID_TITLE_ADMIN_ASSISTANT) + .build(); + assertTrue(uniquePositionList.contains(editedAdminAssistant)); + } + + @Test + public void add_nullPosition_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniquePositionList.add(null)); + } + + @Test + public void add_duplicatePosition_throwsDuplicatePositionException() { + uniquePositionList.add(ADMIN_ASSISTANT); + assertThrows(DuplicatePositionException.class, () -> uniquePositionList.add(ADMIN_ASSISTANT)); + } + + @Test + public void setPosition_nullTargetPosition_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniquePositionList.setPosition(null, ADMIN_ASSISTANT)); + } + + @Test + public void setPosition_nullEditedPosition_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> + uniquePositionList.setPosition(ADMIN_ASSISTANT, null)); + } + + @Test + public void setPosition_targetPositionNotInList_throwsPositionNotFoundException() { + assertThrows(PositionNotFoundException.class, () -> + uniquePositionList.setPosition(ADMIN_ASSISTANT, ADMIN_ASSISTANT)); + } + + @Test + public void setPosition_editedPositionIsSamePosition_success() { + uniquePositionList.add(ADMIN_ASSISTANT); + uniquePositionList.setPosition(ADMIN_ASSISTANT, ADMIN_ASSISTANT); + UniquePositionList expectedUniquePositionList = new UniquePositionList(); + expectedUniquePositionList.add(ADMIN_ASSISTANT); + assertEquals(expectedUniquePositionList, uniquePositionList); + } + + @Test + public void setPosition_editedPositionHasSameIdentity_success() { + uniquePositionList.add(ADMIN_ASSISTANT); + Position editedAdminAssistant = new PositionBuilder(ADMIN_ASSISTANT).build(); + uniquePositionList.setPosition(ADMIN_ASSISTANT, editedAdminAssistant); + UniquePositionList expectedUniquePositionList = new UniquePositionList(); + expectedUniquePositionList.add(editedAdminAssistant); + assertEquals(expectedUniquePositionList, uniquePositionList); + } + + @Test + public void setPosition_editedPositionHasDifferentIdentity_success() { + uniquePositionList.add(ADMIN_ASSISTANT); + uniquePositionList.setPosition(ADMIN_ASSISTANT, BOOKKEEPER); + UniquePositionList expectedUniquePositionList = new UniquePositionList(); + expectedUniquePositionList.add(BOOKKEEPER); + assertEquals(expectedUniquePositionList, uniquePositionList); + } + + @Test + public void setPosition_editedPositionHasNonUniqueIdentity_throwsDuplicatePositionException() { + uniquePositionList.add(ADMIN_ASSISTANT); + uniquePositionList.add(BOOKKEEPER); + assertThrows(DuplicatePositionException.class, () -> + uniquePositionList.setPosition(ADMIN_ASSISTANT, BOOKKEEPER)); + } + + @Test + public void remove_nullPosition_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniquePositionList.remove(null)); + } + + @Test + public void remove_positionDoesNotExist_throwsPositionNotFoundException() { + assertThrows(PositionNotFoundException.class, () -> uniquePositionList.remove(ADMIN_ASSISTANT)); + } + + @Test + public void remove_existingPosition_removesPosition() { + uniquePositionList.add(ADMIN_ASSISTANT); + uniquePositionList.remove(ADMIN_ASSISTANT); + UniquePositionList expectedUniquePositionList = new UniquePositionList(); + assertEquals(expectedUniquePositionList, uniquePositionList); + } + + @Test + public void setPositions_nullUniquePositionList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniquePositionList.setPositions((UniquePositionList) null)); + } + + @Test + public void setPositions_uniquePositionList_replacesOwnListWithProvidedUniquePositionList() { + uniquePositionList.add(ADMIN_ASSISTANT); + UniquePositionList expectedUniquePositionList = new UniquePositionList(); + expectedUniquePositionList.add(BOOKKEEPER); + uniquePositionList.setPositions(expectedUniquePositionList); + assertEquals(expectedUniquePositionList, uniquePositionList); + } + + @Test + public void setPositions_nullList_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> uniquePositionList.setPositions((List<Position>) null)); + } + + @Test + public void setPositions_list_replacesOwnListWithProvidedList() { + uniquePositionList.add(ADMIN_ASSISTANT); + List<Position> positionList = Collections.singletonList(BOOKKEEPER); + uniquePositionList.setPositions(positionList); + UniquePositionList expectedUniquePositionList = new UniquePositionList(); + expectedUniquePositionList.add(BOOKKEEPER); + assertEquals(expectedUniquePositionList, uniquePositionList); + } + + @Test + public void setPositions_listWithDuplicatePositions_throwsDuplicatePositionException() { + List<Position> listWithDuplicatePositions = Arrays.asList(ADMIN_ASSISTANT, ADMIN_ASSISTANT); + assertThrows(DuplicatePositionException.class, () -> + uniquePositionList.setPositions(listWithDuplicatePositions)); + } + + @Test + public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> + uniquePositionList.asUnmodifiableObservableList().remove(0)); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedInterviewTest.java b/src/test/java/seedu/address/storage/JsonAdaptedInterviewTest.java new file mode 100644 index 00000000000..245729708ba --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedInterviewTest.java @@ -0,0 +1,142 @@ +package seedu.address.storage; + +import static seedu.address.storage.JsonAdaptedInterview.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPersons.getTypicalPersons; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.interview.Interview; +import seedu.address.model.interview.Interview.InterviewStatus; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; + +class JsonAdaptedInterviewTest { + private static final String INVALID_TITLE = "Acc-Manager"; + private static final String INVALID_DATE_WRONG_ORDER = "2021/10/15"; + private static final String INVALID_DATE_NOT_A_DATE = "32/13/2100"; + private static final String INVALID_TIME_HAS_COLON = "18:00"; + private static final String INVALID_TIME_NOT_A_TIME = "2600"; + private static final String INVALID_DURATION_NEGATIVE = "-10"; + private static final String INVALID_DURATION_NOT_NUMBER = "Twenty"; + private static final String VALID_DATE = "15/10/2021"; + private static final String VALID_TIME = "1200"; + private static final String VALID_DURATION = "120"; + private static final String VALID_POSITION = ADMIN_ASSISTANT.getTitle().fullTitle; + private static final InterviewStatus VALID_INTERVIEW_STATUS_PENDING = InterviewStatus.PENDING; + private static final Set<String> VALID_CANDIDATE_ID_SET = getTypicalPersons().stream() + .map(p -> String.valueOf(p.hashCode())).collect(Collectors.toSet()); + + @Test + public void toModelType_invalidPositionName_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(INVALID_TITLE, VALID_CANDIDATE_ID_SET, + VALID_DATE, VALID_TIME, VALID_DURATION, VALID_INTERVIEW_STATUS_PENDING); + String expectedMessage = Title.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } + + @Test + public void toModelType_nullPositionName_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(null, VALID_CANDIDATE_ID_SET, + VALID_DATE, VALID_TIME, VALID_DURATION, VALID_INTERVIEW_STATUS_PENDING); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Position.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } + + @Test + public void toModelType_invalidDateFormat_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(VALID_POSITION, VALID_CANDIDATE_ID_SET, + INVALID_DATE_WRONG_ORDER, VALID_TIME, VALID_DURATION, VALID_INTERVIEW_STATUS_PENDING); + String expectedMessage = Interview.MESSAGE_DATE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } + + @Test + public void toModelType_invalidDate_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(VALID_POSITION, VALID_CANDIDATE_ID_SET, + INVALID_DATE_NOT_A_DATE, VALID_TIME, VALID_DURATION, VALID_INTERVIEW_STATUS_PENDING); + String expectedMessage = Interview.MESSAGE_DATE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } + + @Test + public void toModelType_nullDate_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(VALID_POSITION, VALID_CANDIDATE_ID_SET, + null, VALID_TIME, VALID_DURATION, VALID_INTERVIEW_STATUS_PENDING); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "date"); + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } + + @Test + public void toModelType_invalidTimeFormat_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(VALID_POSITION, VALID_CANDIDATE_ID_SET, + VALID_DATE, INVALID_TIME_HAS_COLON, VALID_DURATION, VALID_INTERVIEW_STATUS_PENDING); + String expectedMessage = Interview.MESSAGE_TIME_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } + + @Test + public void toModelType_invalidTime_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(VALID_POSITION, VALID_CANDIDATE_ID_SET, + VALID_DATE, INVALID_TIME_NOT_A_TIME, VALID_DURATION, VALID_INTERVIEW_STATUS_PENDING); + String expectedMessage = Interview.MESSAGE_TIME_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } + + @Test + public void toModelType_nullTime_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(VALID_POSITION, VALID_CANDIDATE_ID_SET, + VALID_DATE, null, VALID_DURATION, VALID_INTERVIEW_STATUS_PENDING); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "time"); + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } + + @Test + public void toModelType_invalidDurationFormat_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(VALID_POSITION, VALID_CANDIDATE_ID_SET, + VALID_DATE, VALID_TIME, INVALID_DURATION_NOT_NUMBER, VALID_INTERVIEW_STATUS_PENDING); + String expectedMessage = Interview.MESSAGE_DURATION_CONSTRAINTS_NOT_A_NUMBER; + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } + + @Test + public void toModelType_invalidDuration_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(VALID_POSITION, VALID_CANDIDATE_ID_SET, + VALID_DATE, VALID_TIME, INVALID_DURATION_NEGATIVE, VALID_INTERVIEW_STATUS_PENDING); + String expectedMessage = Interview.MESSAGE_DURATION_CONSTRAINTS_INVALID_NUMBER; + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } + + @Test + public void toModelType_nullDuration_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(VALID_POSITION, VALID_CANDIDATE_ID_SET, + VALID_DATE, VALID_TIME, null, VALID_INTERVIEW_STATUS_PENDING); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "duration"); + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } + + @Test + public void toModelType_nullStatus_throwsIllegalValueException() { + JsonAdaptedInterview interview = + new JsonAdaptedInterview(VALID_POSITION, VALID_CANDIDATE_ID_SET, + VALID_DATE, VALID_TIME, VALID_DURATION, null); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, InterviewStatus.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, interview::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java index 83b11331cdb..33da4bb3a34 100644 --- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java +++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static seedu.address.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT; import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalInterviews.getTypicalInterviews; import static seedu.address.testutil.TypicalPersons.BENSON; import java.util.ArrayList; @@ -16,6 +17,9 @@ import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; +import seedu.address.model.person.Status; +import seedu.address.model.position.Position.PositionStatus; public class JsonAdaptedPersonTest { private static final String INVALID_NAME = "R@chel"; @@ -23,14 +27,25 @@ public class JsonAdaptedPersonTest { private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; private static final String INVALID_TAG = "#friend"; + private static final String INVALID_POSITION = "#Security"; private static final String VALID_NAME = BENSON.getName().toString(); private static final String VALID_PHONE = BENSON.getPhone().toString(); private static final String VALID_EMAIL = BENSON.getEmail().toString(); private static final String VALID_ADDRESS = BENSON.getAddress().toString(); + private static final String VALID_REMARK = BENSON.getRemark().toString(); + private static final String VALID_STATUS = BENSON.getStatus().toString(); + private static final PositionStatus VALID_POSITION_STATUS = PositionStatus.OPEN; private static final List<JsonAdaptedTag> VALID_TAGS = BENSON.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList()); + private static final List<JsonAdaptedPosition> VALID_POSITIONS = BENSON.getPositions().stream() + .map(JsonAdaptedPosition::new) + .collect(Collectors.toList()); + + private static final List<JsonAdaptedInterview> VALID_INTERVIEWS = getTypicalInterviews().stream() + .map(JsonAdaptedInterview::new) + .collect(Collectors.toList()); @Test public void toModelType_validPersonDetails_returnsPerson() throws Exception { @@ -41,14 +56,17 @@ public void toModelType_validPersonDetails_returnsPerson() throws Exception { @Test public void toModelType_invalidName_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_REMARK, + VALID_TAGS, VALID_STATUS, VALID_POSITIONS); String expectedMessage = Name.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullName_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedPerson person = + new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_REMARK, VALID_TAGS, + VALID_STATUS, VALID_POSITIONS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -56,14 +74,17 @@ public void toModelType_nullName_throwsIllegalValueException() { @Test public void toModelType_invalidPhone_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_REMARK, + VALID_TAGS, VALID_STATUS, VALID_POSITIONS); String expectedMessage = Phone.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullPhone_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_REMARK, + VALID_TAGS, VALID_STATUS, VALID_POSITIONS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -71,14 +92,17 @@ public void toModelType_nullPhone_throwsIllegalValueException() { @Test public void toModelType_invalidEmail_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_REMARK, + VALID_TAGS, VALID_STATUS, VALID_POSITIONS); String expectedMessage = Email.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullEmail_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_REMARK, + VALID_TAGS, VALID_STATUS, VALID_POSITIONS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -86,14 +110,17 @@ public void toModelType_nullEmail_throwsIllegalValueException() { @Test public void toModelType_invalidAddress_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS); + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_REMARK, + VALID_TAGS, VALID_STATUS, VALID_POSITIONS); String expectedMessage = Address.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullAddress_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS); + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_REMARK, + VALID_TAGS, VALID_STATUS, VALID_POSITIONS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -103,8 +130,37 @@ public void toModelType_invalidTags_throwsIllegalValueException() { List<JsonAdaptedTag> invalidTags = new ArrayList<>(VALID_TAGS); invalidTags.add(new JsonAdaptedTag(INVALID_TAG)); JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags); + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_REMARK, + invalidTags, VALID_STATUS, VALID_POSITIONS); + assertThrows(IllegalValueException.class, person::toModelType); + } + + + @Test + public void toModelType_nullRemark_throwsIllegalValueException() { + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, null, VALID_TAGS, + VALID_STATUS, VALID_POSITIONS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Remark.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidPositions_throwsIllegalValueException() { + List<JsonAdaptedPosition> invalidPositions = new ArrayList<>(VALID_POSITIONS); + invalidPositions.add(new JsonAdaptedPosition(INVALID_POSITION, VALID_POSITION_STATUS)); + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_REMARK, VALID_TAGS, + VALID_STATUS, invalidPositions); assertThrows(IllegalValueException.class, person::toModelType); } + @Test + public void toModelType_nullStatus_throwsIllegalValueException() { + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_REMARK, VALID_TAGS, + null, VALID_POSITIONS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Status.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } } diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPositionTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPositionTest.java new file mode 100644 index 00000000000..f0fc421de2c --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedPositionTest.java @@ -0,0 +1,50 @@ +package seedu.address.storage; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.storage.JsonAdaptedPosition.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.position.Position.PositionStatus; +import seedu.address.model.position.Title; + +class JsonAdaptedPositionTest { + private static final String INVALID_TITLE = "Acc-Manager"; + private static final String VALID_TITLE = ADMIN_ASSISTANT.getTitle().fullTitle; + private static final PositionStatus VALID_OPEN_POSITION_STATUS = PositionStatus.OPEN; + + @Test + public void toModelType_validPositionDetails_returnsPosition() throws Exception { + JsonAdaptedPosition position = new JsonAdaptedPosition(ADMIN_ASSISTANT); + assertEquals(ADMIN_ASSISTANT, position.toModelType()); + } + + @Test + public void toModelType_invalidTitle_throwsIllegalValueException() { + JsonAdaptedPosition position = + new JsonAdaptedPosition(INVALID_TITLE, VALID_OPEN_POSITION_STATUS); + String expectedMessage = Title.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, position::toModelType); + } + + @Test + public void toModelType_nullTitle_throwsIllegalValueException() { + JsonAdaptedPosition position = + new JsonAdaptedPosition((String) null, VALID_OPEN_POSITION_STATUS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName()); + assertThrows(IllegalValueException.class, expectedMessage, position::toModelType); + } + + + @Test + public void toModelType_validDetails_returnsSameTitle() { + JsonAdaptedPosition position = + new JsonAdaptedPosition(VALID_TITLE, VALID_OPEN_POSITION_STATUS); + + assertEquals(ADMIN_ASSISTANT.getTitle().fullTitle, position.getTitle()); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedTitleTest.java b/src/test/java/seedu/address/storage/JsonAdaptedTitleTest.java new file mode 100644 index 00000000000..9d83c43cd98 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonAdaptedTitleTest.java @@ -0,0 +1,43 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPositions.ADMIN_ASSISTANT; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.position.Title; + +class JsonAdaptedTitleTest { + private static final String INVALID_TITLE = "Acc-Manager"; + + + @Test + public void toModelType_validTitleDetails_returnsTitle() throws Exception { + JsonAdaptedTitle title = new JsonAdaptedTitle(ADMIN_ASSISTANT.getTitle()); + assertEquals(ADMIN_ASSISTANT.title, title.toModelType()); + } + + @Test + public void toModelType_invalidTitleName_throwsIllegalValueException() { + JsonAdaptedTitle title = + new JsonAdaptedTitle(INVALID_TITLE); + String expectedMessage = Title.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, title::toModelType); + } + + @Test + public void toModelType_nullTitleName_throwsIllegalValueException() { + JsonAdaptedTitle title = + new JsonAdaptedTitle(""); + String expectedMessage = Title.MESSAGE_CONSTRAINTS; + assertThrows(IllegalValueException.class, expectedMessage, title::toModelType); + } + + @Test + public void toModelType_validTitleDetails_returnsSameTitle() throws Exception { + JsonAdaptedTitle title = new JsonAdaptedTitle("Administrative Assistant"); + assertEquals(ADMIN_ASSISTANT.getTitle().fullTitle, title.getTitle()); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java deleted file mode 100644 index ac3c3af9566..00000000000 --- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package seedu.address.storage; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.HOON; -import static seedu.address.testutil.TypicalPersons.IDA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; - -public class JsonAddressBookStorageTest { - private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonAddressBookStorageTest"); - - @TempDir - public Path testFolder; - - @Test - public void readAddressBook_nullFilePath_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> readAddressBook(null)); - } - - private java.util.Optional<ReadOnlyAddressBook> readAddressBook(String filePath) throws Exception { - return new JsonAddressBookStorage(Paths.get(filePath)).readAddressBook(addToTestDataPathIfNotNull(filePath)); - } - - private Path addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { - return prefsFileInTestDataFolder != null - ? TEST_DATA_FOLDER.resolve(prefsFileInTestDataFolder) - : null; - } - - @Test - public void read_missingFile_emptyResult() throws Exception { - assertFalse(readAddressBook("NonExistentFile.json").isPresent()); - } - - @Test - public void read_notJsonFormat_exceptionThrown() { - assertThrows(DataConversionException.class, () -> readAddressBook("notJsonFormatAddressBook.json")); - } - - @Test - public void readAddressBook_invalidPersonAddressBook_throwDataConversionException() { - assertThrows(DataConversionException.class, () -> readAddressBook("invalidPersonAddressBook.json")); - } - - @Test - public void readAddressBook_invalidAndValidPersonAddressBook_throwDataConversionException() { - assertThrows(DataConversionException.class, () -> readAddressBook("invalidAndValidPersonAddressBook.json")); - } - - @Test - public void readAndSaveAddressBook_allInOrder_success() throws Exception { - Path filePath = testFolder.resolve("TempAddressBook.json"); - AddressBook original = getTypicalAddressBook(); - JsonAddressBookStorage jsonAddressBookStorage = new JsonAddressBookStorage(filePath); - - // Save in new file and read back - jsonAddressBookStorage.saveAddressBook(original, filePath); - ReadOnlyAddressBook readBack = jsonAddressBookStorage.readAddressBook(filePath).get(); - assertEquals(original, new AddressBook(readBack)); - - // Modify data, overwrite exiting file, and read back - original.addPerson(HOON); - original.removePerson(ALICE); - jsonAddressBookStorage.saveAddressBook(original, filePath); - readBack = jsonAddressBookStorage.readAddressBook(filePath).get(); - assertEquals(original, new AddressBook(readBack)); - - // Save and read without specifying file path - original.addPerson(IDA); - jsonAddressBookStorage.saveAddressBook(original); // file path not specified - readBack = jsonAddressBookStorage.readAddressBook().get(); // file path not specified - assertEquals(original, new AddressBook(readBack)); - - } - - @Test - public void saveAddressBook_nullAddressBook_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> saveAddressBook(null, "SomeFile.json")); - } - - /** - * Saves {@code addressBook} at the specified {@code filePath}. - */ - private void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) { - try { - new JsonAddressBookStorage(Paths.get(filePath)) - .saveAddressBook(addressBook, addToTestDataPathIfNotNull(filePath)); - } catch (IOException ioe) { - throw new AssertionError("There should not be an error writing to the file.", ioe); - } - } - - @Test - public void saveAddressBook_nullFilePath_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> saveAddressBook(new AddressBook(), null)); - } -} diff --git a/src/test/java/seedu/address/storage/JsonHrManagerStorageTest.java b/src/test/java/seedu/address/storage/JsonHrManagerStorageTest.java new file mode 100644 index 00000000000..136ca808717 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonHrManagerStorageTest.java @@ -0,0 +1,172 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalPersons.ALICE; +import static seedu.address.testutil.TypicalPersons.HOON; +import static seedu.address.testutil.TypicalPersons.IDA; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.HrManager; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.testutil.InterviewBuilder; + +public class JsonHrManagerStorageTest { + private static final Path TEST_DATA_FOLDER = + Paths.get("src", "test", "data", "JsonHrManagerStorageTest"); + + @TempDir + public Path testFolder; + + @Test + public void readHrManager_nullFilePath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> readHrManager(null, + null, null)); + } + + private java.util.Optional<ReadOnlyHrManager> readHrManager(String candidatesFilePath, + String positionsFilePath, + String interviewsFilePath) throws Exception { + return new JsonHrManagerStorage(Paths.get(candidatesFilePath), Paths.get(positionsFilePath), + Paths.get(interviewsFilePath)) + .readHrManager(addToTestDataPathIfNotNull(candidatesFilePath), + addToTestDataPathIfNotNull(positionsFilePath), + addToTestDataPathIfNotNull(interviewsFilePath)); + } + + private Path addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { + return prefsFileInTestDataFolder != null + ? TEST_DATA_FOLDER.resolve(prefsFileInTestDataFolder) + : null; + } + + @Test + public void read_missingFile_emptyResult() throws Exception { + assertFalse(readHrManager("NonExistentCandidateFile.json", + "NonExistentPositionFile.json", + "NonExistentInterviewFile.json").isPresent()); + } + + @Test + public void read_notJsonFormat_exceptionThrown() { + assertThrows(DataConversionException.class, () -> readHrManager( + "notJsonFormatHrManagerCandidates.json", + "notJsonFormatHrManagerPositions.json", + "notJsonFormatHrManagerInterviews.json")); + } + + @Test + public void readHrManager_invalidAllHrManager_throwDataConversionException() { + assertThrows(DataConversionException.class, () -> readHrManager("invalidPersonHrManager.json", + "invalidPositionHrManager.json", "invalidInterviewHrManager.json")); + } + + @Test + public void readHrManager_invalidAndValidAllHrManager_throwDataConversionException() { + assertThrows(DataConversionException.class, () -> readHrManager( + "invalidAndValidPersonHrManager.json", + "invalidAndValidPositionHrManager.json", + "invalidAndValidInterviewHrManager.json")); + } + + @Test + public void readHrManager_invalidPersonHrManagerEntry_throwDataConversionException() { + assertThrows(DataConversionException.class, () -> readHrManager( + "invalidAndValidPersonHrManager.json", + "validPositionHrManager.json", + "validInterviewHrManager.json")); + } + + @Test + public void readHrManager_invalidPositionHrManagerEntry_throwDataConversionException() { + assertThrows(DataConversionException.class, () -> readHrManager( + "validPersonHrManager.json", + "invalidAndValidPositionHrManager.json", + "validInterviewHrManager.json")); + } + + @Test + public void readHrManager_invalidInterviewHrManagerEntry_throwDataConversionException() { + assertThrows(DataConversionException.class, () -> readHrManager( + "validPersonHrManager.json", + "validAndValidPositionHrManager.json", + "invalidAndValidInterviewHrManager.json")); + } + + + @Test + public void readAndSaveHrManager_allInOrder_success() throws Exception { + Path candidatesFilePath = testFolder.resolve("HrManagerCandidates.json"); + Path positionsFilePath = testFolder.resolve("HrManagerPositions.json"); + Path interviewsFilePath = testFolder.resolve("HrManagerInterviews.json"); + HrManager original = getTypicalHrManager(); + JsonHrManagerStorage jsonHrManagerStorage = new JsonHrManagerStorage(candidatesFilePath, positionsFilePath, + interviewsFilePath); + + // Save in new file and read back + jsonHrManagerStorage.saveHrManager(original, candidatesFilePath, positionsFilePath, interviewsFilePath); + ReadOnlyHrManager readBack = jsonHrManagerStorage.readHrManager(candidatesFilePath, positionsFilePath, + interviewsFilePath).get(); + + assertEquals(original, new HrManager(readBack)); + + // Modify data, overwrite exiting file, and read back + original.addPerson(HOON); + original.removePerson(ALICE); + jsonHrManagerStorage.saveHrManager(original, candidatesFilePath, positionsFilePath, interviewsFilePath); + readBack = jsonHrManagerStorage.readHrManager(candidatesFilePath, positionsFilePath, + interviewsFilePath).get(); + + assertEquals(original, new HrManager(readBack)); + + // Save and read without specifying file path + original.addPerson(IDA); + jsonHrManagerStorage.saveHrManager(original); // file path not specified + readBack = jsonHrManagerStorage.readHrManager().get(); // file path not specified + assertEquals(original, new HrManager(readBack)); + + //add interview save and read back + original.addInterview(new InterviewBuilder().build()); + jsonHrManagerStorage.saveHrManager(original); // file path not specified + readBack = jsonHrManagerStorage.readHrManager().get(); // file path not specified + assertEquals(original, new HrManager(readBack)); + + } + + @Test + public void saveHrManager_nullHrManager_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> saveHrManager(null, + "SomeCandidatesFile.json", "SomePositionsFile.json", + "SomeInterviewsFile.json")); + } + + /** + * Saves {@code hrManager} at the specified {@code filePath}. + */ + private void saveHrManager(ReadOnlyHrManager hrManager, String candidatesFilePath, String positionsFilePath, + String interviewsFilePath) { + try { + new JsonHrManagerStorage(Paths.get(candidatesFilePath), Paths.get(positionsFilePath), + Paths.get(interviewsFilePath)).saveHrManager(hrManager, + addToTestDataPathIfNotNull(candidatesFilePath), addToTestDataPathIfNotNull(positionsFilePath), + addToTestDataPathIfNotNull(interviewsFilePath)); + } catch (IOException ioe) { + throw new AssertionError("There should not be an error writing to the file.", ioe); + } + } + + @Test + public void saveHrManager_nullFilePath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> saveHrManager(new HrManager(), + null, null, null)); + } +} diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableHrManagerCandidatesTest.java similarity index 53% rename from src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java rename to src/test/java/seedu/address/storage/JsonSerializableHrManagerCandidatesTest.java index 188c9058d20..4b99b45e0a4 100644 --- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java +++ b/src/test/java/seedu/address/storage/JsonSerializableHrManagerCandidatesTest.java @@ -10,37 +10,39 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.JsonUtil; -import seedu.address.model.AddressBook; +import seedu.address.model.HrManager; import seedu.address.testutil.TypicalPersons; -public class JsonSerializableAddressBookTest { +public class JsonSerializableHrManagerCandidatesTest { - private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableAddressBookTest"); - private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsAddressBook.json"); - private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonAddressBook.json"); - private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonAddressBook.json"); + private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", + "JsonSerializableHrManagerCandidatesTest"); + private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsHrManager.json"); + private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonHrManager.json"); + private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonHrManager.json"); @Test public void toModelType_typicalPersonsFile_success() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE, - JsonSerializableAddressBook.class).get(); - AddressBook addressBookFromFile = dataFromFile.toModelType(); - AddressBook typicalPersonsAddressBook = TypicalPersons.getTypicalAddressBook(); + //this test case reads only persons + JsonSerializableHrManagerCandidates dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE, + JsonSerializableHrManagerCandidates.class).get(); + HrManager addressBookFromFile = dataFromFile.toModelType(); + HrManager typicalPersonsAddressBook = TypicalPersons.getTypicalHrManagerWithOnlyTypicalPersons(); assertEquals(addressBookFromFile, typicalPersonsAddressBook); } @Test public void toModelType_invalidPersonFile_throwsIllegalValueException() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_PERSON_FILE, - JsonSerializableAddressBook.class).get(); + JsonSerializableHrManagerCandidates dataFromFile = JsonUtil.readJsonFile(INVALID_PERSON_FILE, + JsonSerializableHrManagerCandidates.class).get(); assertThrows(IllegalValueException.class, dataFromFile::toModelType); } @Test public void toModelType_duplicatePersons_throwsIllegalValueException() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_PERSON_FILE, - JsonSerializableAddressBook.class).get(); - assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_PERSON, + JsonSerializableHrManagerCandidates dataFromFile = JsonUtil.readJsonFile(DUPLICATE_PERSON_FILE, + JsonSerializableHrManagerCandidates.class).get(); + assertThrows(IllegalValueException.class, JsonSerializableHrManagerCandidates.MESSAGE_DUPLICATE_PERSON, dataFromFile::toModelType); } diff --git a/src/test/java/seedu/address/storage/JsonSerializableHrManagerInterviewsTest.java b/src/test/java/seedu/address/storage/JsonSerializableHrManagerInterviewsTest.java new file mode 100644 index 00000000000..1e444c92804 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonSerializableHrManagerInterviewsTest.java @@ -0,0 +1,47 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.testutil.Assert.assertThrows; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.JsonUtil; +import seedu.address.model.HrManager; +import seedu.address.testutil.TypicalPersons; + +class JsonSerializableHrManagerInterviewsTest { + private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", + "JsonSerializableHrManagerInterviewsTest"); + private static final Path TYPICAL_INTERVIEWS_FILE = TEST_DATA_FOLDER.resolve("typicalInterviewHrManager.json"); + private static final Path INVALID_INTERVIEWS_FILE = TEST_DATA_FOLDER.resolve("invalidInterviewHrManager.json"); + private static final Path DUPLICATE_INTERVIEWS_FILE = TEST_DATA_FOLDER.resolve("duplicateInterviewHrManager.json"); + + @Test + public void toModelType_typicalInterviewsFile_success() throws Exception { + //this test case reads only interviews + JsonSerializableHrManagerInterviews dataFromFile = JsonUtil.readJsonFile(TYPICAL_INTERVIEWS_FILE, + JsonSerializableHrManagerInterviews.class).get(); + HrManager hrManagerFromFile = dataFromFile.toModelType(); + HrManager typicalInterviewsHrManager = TypicalPersons.getTypicalHrManagerWithOnlyTypicalInterviews(); + assertEquals(hrManagerFromFile, typicalInterviewsHrManager); + } + + @Test + public void toModelType_invalidPersonFile_throwsIllegalValueException() throws Exception { + JsonSerializableHrManagerInterviews dataFromFile = JsonUtil.readJsonFile(INVALID_INTERVIEWS_FILE, + JsonSerializableHrManagerInterviews.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + + @Test + public void toModelType_duplicatePersons_throwsIllegalValueException() throws Exception { + JsonSerializableHrManagerInterviews dataFromFile = JsonUtil.readJsonFile(DUPLICATE_INTERVIEWS_FILE, + JsonSerializableHrManagerInterviews.class).get(); + assertThrows(IllegalValueException.class, JsonSerializableHrManagerInterviews.MESSAGE_DUPLICATE_INTERVIEWS, + dataFromFile::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonSerializableHrManagerPositionsTest.java b/src/test/java/seedu/address/storage/JsonSerializableHrManagerPositionsTest.java new file mode 100644 index 00000000000..30122b21bb6 --- /dev/null +++ b/src/test/java/seedu/address/storage/JsonSerializableHrManagerPositionsTest.java @@ -0,0 +1,49 @@ +package seedu.address.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.address.testutil.Assert.assertThrows; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.JsonUtil; +import seedu.address.model.HrManager; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; + +public class JsonSerializableHrManagerPositionsTest { + private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", + "JsonSerializableHrManagerPositionsTest"); + private static final Path TYPICAL_POSITIONS_FILE = TEST_DATA_FOLDER.resolve("typicalPositionsHrManager.json"); + private static final Path INVALID_POSITION_FILE = TEST_DATA_FOLDER.resolve("invalidPositionHrManager.json"); + private static final Path DUPLICATE_POSITION_FILE = TEST_DATA_FOLDER.resolve("duplicatePositionHrManager.json"); + + @Test + public void toModelType_typicalPositionsFile_success() throws Exception { + JsonSerializableHrManagerPositions dataFromFile = JsonUtil.readJsonFile(TYPICAL_POSITIONS_FILE, + JsonSerializableHrManagerPositions.class).get(); + HrManager addressBookFromFile = dataFromFile.toModelType(); + HrManager typicalPersonsAddressBook = new HrManager(); + typicalPersonsAddressBook.addPosition(new Position(new Title("Bookkeeper"))); + typicalPersonsAddressBook.addPosition(new Position(new Title("Administrative Assistant"))); + assertEquals(addressBookFromFile, typicalPersonsAddressBook); + } + + @Test + public void toModelType_invalidPositionFile_throwsIllegalValueException() throws Exception { + JsonSerializableHrManagerPositions dataFromFile = JsonUtil.readJsonFile(INVALID_POSITION_FILE, + JsonSerializableHrManagerPositions.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + + @Test + public void toModelType_duplicatePositions_throwsIllegalValueException() throws Exception { + JsonSerializableHrManagerPositions dataFromFile = JsonUtil.readJsonFile(DUPLICATE_POSITION_FILE, + JsonSerializableHrManagerPositions.class).get(); + assertThrows(IllegalValueException.class, JsonSerializableHrManagerPositions.MESSAGE_DUPLICATE_POSITIONS, + dataFromFile::toModelType); + } +} diff --git a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java b/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java index 16f33f4a6bb..06617df6876 100644 --- a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java +++ b/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java @@ -73,7 +73,7 @@ public void readUserPrefs_extraValuesInFile_extraValuesIgnored() throws DataConv private UserPrefs getTypicalUserPrefs() { UserPrefs userPrefs = new UserPrefs(); userPrefs.setGuiSettings(new GuiSettings(1000, 500, 300, 100)); - userPrefs.setAddressBookFilePath(Paths.get("addressbook.json")); + userPrefs.setHrManagerCandidatesFilePath(Paths.get("data/candidates.json")); return userPrefs; } diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java index 99a16548970..ee2d8773836 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/seedu/address/storage/StorageManagerTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.TypicalPersons.getTypicalHrManager; import java.nio.file.Path; @@ -11,8 +11,8 @@ import org.junit.jupiter.api.io.TempDir; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.HrManager; +import seedu.address.model.ReadOnlyHrManager; import seedu.address.model.UserPrefs; public class StorageManagerTest { @@ -24,9 +24,10 @@ public class StorageManagerTest { @BeforeEach public void setUp() { - JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage(getTempFilePath("ab")); + JsonHrManagerStorage hrManagerStorage = new JsonHrManagerStorage(getTempFilePath("abc"), + getTempFilePath("abp"), getTempFilePath("abi")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(getTempFilePath("prefs")); - storageManager = new StorageManager(addressBookStorage, userPrefsStorage); + storageManager = new StorageManager(hrManagerStorage, userPrefsStorage); } private Path getTempFilePath(String fileName) { @@ -48,21 +49,36 @@ public void prefsReadSave() throws Exception { } @Test - public void addressBookReadSave() throws Exception { + public void hrManagerReadSave() throws Exception { /* * Note: This is an integration test that verifies the StorageManager is properly wired to the - * {@link JsonAddressBookStorage} class. - * More extensive testing of UserPref saving/reading is done in {@link JsonAddressBookStorageTest} class. + * {@link JsonHrManagerStorage} class. + * More extensive testing of UserPref saving/reading is done in {@link JsonHrManagerStorageTest} class. */ - AddressBook original = getTypicalAddressBook(); - storageManager.saveAddressBook(original); - ReadOnlyAddressBook retrieved = storageManager.readAddressBook().get(); - assertEquals(original, new AddressBook(retrieved)); + HrManager original = getTypicalHrManager(); + storageManager.saveHrManager(original); + ReadOnlyHrManager retrieved = storageManager.readHrManager().get(); + assertEquals(original, new HrManager(retrieved)); } @Test - public void getAddressBookFilePath() { - assertNotNull(storageManager.getAddressBookFilePath()); + public void getHrManagerCandidatesFilePath() { + assertNotNull(storageManager.getHrManagerCandidatesFilePath()); + } + + @Test + public void getHrManagerPositionsFilePath() { + assertNotNull(storageManager.getHrManagerPositionsFilePath()); + } + + @Test + public void getHrManagerInterviewsFilePath() { + assertNotNull(storageManager.getHrManagerInterviewsFilePath()); + } + + @Test + public void getUserPrefsFilePath() { + assertNotNull(storageManager.getUserPrefsFilePath()); } } diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java deleted file mode 100644 index d53799fd110..00000000000 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ /dev/null @@ -1,34 +0,0 @@ -package seedu.address.testutil; - -import seedu.address.model.AddressBook; -import seedu.address.model.person.Person; - -/** - * A utility class to help with building Addressbook objects. - * Example usage: <br> - * {@code AddressBook ab = new AddressBookBuilder().withPerson("John", "Doe").build();} - */ -public class AddressBookBuilder { - - private AddressBook addressBook; - - public AddressBookBuilder() { - addressBook = new AddressBook(); - } - - public AddressBookBuilder(AddressBook addressBook) { - this.addressBook = addressBook; - } - - /** - * Adds a new {@code Person} to the {@code AddressBook} that we are building. - */ - public AddressBookBuilder withPerson(Person person) { - addressBook.addPerson(person); - return this; - } - - public AddressBook build() { - return addressBook; - } -} diff --git a/src/test/java/seedu/address/testutil/EditInterviewDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditInterviewDescriptorBuilder.java new file mode 100644 index 00000000000..df2b211cd8e --- /dev/null +++ b/src/test/java/seedu/address/testutil/EditInterviewDescriptorBuilder.java @@ -0,0 +1,90 @@ +package seedu.address.testutil; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; + +import seedu.address.logic.interview.EditInterviewCommand.EditInterviewDescriptor; +import seedu.address.model.interview.Interview; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; + +/** + * A utility class to help with building EditInterviewDescriptor objects. + */ +public class EditInterviewDescriptorBuilder { + + private EditInterviewDescriptor descriptor; + + public EditInterviewDescriptorBuilder() { + descriptor = new EditInterviewDescriptor(); + } + + public EditInterviewDescriptorBuilder(EditInterviewDescriptor descriptor) { + this.descriptor = new EditInterviewDescriptor(descriptor); + } + + /** + * Returns an {@code EditInterviewDescriptor} with fields containing {@code Interview}'s details. + */ + public EditInterviewDescriptorBuilder(Interview interview) { + descriptor = new EditInterviewDescriptor(); + descriptor.setPosition(interview.getPosition()); + descriptor.setDate(interview.getDate()); + descriptor.setStartTime(interview.getStartTime()); + descriptor.setDuration(interview.getDuration()); + descriptor.setStatus(interview.getStatus()); + } + + /** + * Sets the {@code Position} of the {@code EditInterviewDescriptor}. + */ + public EditInterviewDescriptorBuilder withPosition(String position) { + descriptor.setPosition(new Position(new Title(position))); + return this; + } + + /** + * Sets the {@code LocalDate} of the {@code EditInterviewDescriptor}. + */ + public EditInterviewDescriptorBuilder withDate(String date) { + String[] foundDate = date.split("/"); + int year = Integer.parseInt(foundDate[2]); + int month = Integer.parseInt(foundDate[1]); + int day = Integer.parseInt(foundDate[0]); + descriptor.setDate(LocalDate.of(year, month, day)); + return this; + } + + /** + * Sets the {@code LocalTime} of the {@code EditInterviewDescriptor}. + */ + public EditInterviewDescriptorBuilder withStartTime(String time) { + int hour = Integer.parseInt(time.substring(0, 2)); + int min = Integer.parseInt(time.substring(2)); + descriptor.setStartTime(LocalTime.of(hour, min)); + return this; + } + + /** + * Sets the {@code Duration} of the {@code EditInterviewDescriptor}. + */ + public EditInterviewDescriptorBuilder withDuration(String duration) { + Long actualDuration = Long.parseLong(duration); + descriptor.setDuration(Duration.ofMinutes(actualDuration)); + return this; + } + + /** + * Sets the {@code InterviewStatus} of the {@code EditInterviewDescriptor}. + */ + public EditInterviewDescriptorBuilder withStatus(Interview.InterviewStatus status) { + descriptor.setStatus(status); + return this; + } + + public EditInterviewDescriptor build() { + return descriptor; + } + +} diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java index 4584bd5044e..e2362da32b8 100644 --- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java +++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java @@ -4,12 +4,15 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.candidate.EditCandidateCommand.EditPersonDescriptor; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Status; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; import seedu.address.model.tag.Tag; /** @@ -37,6 +40,8 @@ public EditPersonDescriptorBuilder(Person person) { descriptor.setEmail(person.getEmail()); descriptor.setAddress(person.getAddress()); descriptor.setTags(person.getTags()); + descriptor.setPositions(person.getPositions()); + descriptor.setStatus(person.getStatus()); } /** @@ -81,6 +86,25 @@ public EditPersonDescriptorBuilder withTags(String... tags) { return this; } + /** + * Parses the {@code positions} into a {@code Set<Position>} and set it to the {@code EditPersonDescriptor} + * that we are building. + */ + public EditPersonDescriptorBuilder withPositions(String... positions) { + Set<Position> positionSet = Stream.of(positions).map(Title::new).map(Position::new).collect(Collectors.toSet()); + descriptor.setPositions(positionSet); + return this; + } + + /** + * Sets the {@code Name} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withStatus(Status status) { + descriptor.setStatus(status); + return this; + } + + public EditPersonDescriptor build() { return descriptor; } diff --git a/src/test/java/seedu/address/testutil/EditPositionDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPositionDescriptorBuilder.java new file mode 100644 index 00000000000..347682eb6f7 --- /dev/null +++ b/src/test/java/seedu/address/testutil/EditPositionDescriptorBuilder.java @@ -0,0 +1,51 @@ +package seedu.address.testutil; + +import seedu.address.logic.position.EditPositionCommand; +import seedu.address.model.position.Position; +import seedu.address.model.position.Position.PositionStatus; +import seedu.address.model.position.Title; + +/** + * A utility class to help with building EditPositionDescriptor objects. + */ +public class EditPositionDescriptorBuilder { + + private EditPositionCommand.EditPositionDescriptor descriptor; + + public EditPositionDescriptorBuilder() { + descriptor = new EditPositionCommand.EditPositionDescriptor(); + } + + public EditPositionDescriptorBuilder(EditPositionCommand.EditPositionDescriptor descriptor) { + this.descriptor = new EditPositionCommand.EditPositionDescriptor(descriptor); + } + + /** + * Returns an {@code EditPositionDescriptor} with fields containing {@code position}'s details + */ + public EditPositionDescriptorBuilder(Position position) { + descriptor = new EditPositionCommand.EditPositionDescriptor(); + descriptor.setTitle(position.getTitle()); + descriptor.setPositionStatus(position.getStatus()); + } + + /** + * Sets the {@code Title} of the {@code EditPositionDescriptor} that we are building. + */ + public EditPositionDescriptorBuilder withTitle(String title) { + descriptor.setTitle(new Title(title)); + return this; + } + + /** + * Sets the {@code Status} of the {@code EditPositionDescriptor} that we are building. + */ + public EditPositionDescriptorBuilder withPositionStatus(PositionStatus status) { + descriptor.setPositionStatus(status); + return this; + } + + public EditPositionCommand.EditPositionDescriptor build() { + return descriptor; + } +} diff --git a/src/test/java/seedu/address/testutil/HrManagerBuilder.java b/src/test/java/seedu/address/testutil/HrManagerBuilder.java new file mode 100644 index 00000000000..b239c9dbda4 --- /dev/null +++ b/src/test/java/seedu/address/testutil/HrManagerBuilder.java @@ -0,0 +1,52 @@ +package seedu.address.testutil; + +import seedu.address.model.HrManager; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; + +/** + * A utility class to help with building HrManager objects. + * Example usage: <br> + * {@code HrManager hr = new HrManagerBuilder().withPerson("John", "Doe").build();} + */ +public class HrManagerBuilder { + + private HrManager hrManager; + + public HrManagerBuilder() { + hrManager = new HrManager(); + } + + public HrManagerBuilder(HrManager hrManager) { + this.hrManager = hrManager; + } + + /** + * Adds a new {@code Person} to the {@code HrManager} that we are building. + */ + public HrManagerBuilder withPerson(Person person) { + hrManager.addPerson(person); + return this; + } + + /** + * Adds a new {@code Position} to the {@code HrManager} that we are building. + */ + public HrManagerBuilder withPosition(Position position) { + hrManager.addPosition(position); + return this; + } + + /** + * Adds a new {@code Interview} to the {@code HrManager} that we are building. + */ + public HrManagerBuilder withInterview(Interview interview) { + hrManager.addInterview(interview); + return this; + } + + public HrManager build() { + return hrManager; + } +} diff --git a/src/test/java/seedu/address/testutil/InterviewBuilder.java b/src/test/java/seedu/address/testutil/InterviewBuilder.java new file mode 100644 index 00000000000..9372dbc0850 --- /dev/null +++ b/src/test/java/seedu/address/testutil/InterviewBuilder.java @@ -0,0 +1,95 @@ +package seedu.address.testutil; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.HashSet; +import java.util.Set; + +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; + +/** + * A utility class to help with building Interview objects. + */ +public class InterviewBuilder { + + + private Position position = TypicalPositions.HR_MANAGER; + private Set<Person> candidates = new HashSet<>(); + private LocalDate localDate = LocalDate.of(2021, 10, 15); + private LocalTime startTime = LocalTime.of(10, 0); + private Duration duration = Duration.ofHours(1); + private Interview.InterviewStatus status = Interview.InterviewStatus.PENDING; + + /** + * Creates a {@code InterviewBuilder} with the default details. + */ + public InterviewBuilder() { + } + + /** + * Initializes the InterviewBuilder with the data of {@code interviewToCopy}. + */ + public InterviewBuilder(Interview interviewToCopy) { + position = new Position(interviewToCopy.getPositionTitle()); + candidates = interviewToCopy.getCandidates(); + startTime = interviewToCopy.getStartTime(); + duration = interviewToCopy.getDuration(); + status = interviewToCopy.getStatus(); + } + + /** + * Sets the {@code Position} of the {@code Interview} that we are building. + */ + public InterviewBuilder withPosition(Position position) { + this.position = position; + return this; + } + + /** + * Sets the {@code Candidates} of the {@code Interview} that we are building. + */ + public InterviewBuilder withCandidates(Set<Person> candidates) { + this.candidates = candidates; + return this; + } + + /** + * Sets the {@code localDate} of the {@code Interview} that we are building. + */ + public InterviewBuilder withDate(LocalDate date) { + this.localDate = date; + return this; + } + + /** + * Sets the {@code StartTime} of the {@code Interview} that we are building. + */ + public InterviewBuilder withStartTime(LocalTime startTime) { + this.startTime = startTime; + return this; + } + + /** + * Sets the {@code Duration} of the {@code Interview} that we are building. + */ + public InterviewBuilder withDuration(Duration duration) { + this.duration = duration; + return this; + } + + /** + * Sets the {@code Status} of the {@code Interview} that we are building. + */ + public InterviewBuilder withStatus(Interview.InterviewStatus status) { + this.status = status; + return this; + } + + public Interview build() { + return new Interview(position, candidates, localDate, startTime, duration, status); + } + +} diff --git a/src/test/java/seedu/address/testutil/InterviewUtil.java b/src/test/java/seedu/address/testutil/InterviewUtil.java new file mode 100644 index 00000000000..7ee79e70c36 --- /dev/null +++ b/src/test/java/seedu/address/testutil/InterviewUtil.java @@ -0,0 +1,37 @@ +package seedu.address.testutil; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_CANDIDATE_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INTERVIEW_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME; + +import seedu.address.logic.interview.AddInterviewCommand; +import seedu.address.model.interview.Interview; + +public class InterviewUtil { + + /** + * Returns an add interview command string for adding the {@code interview}. + */ + public static String getAddInterviewCommand(Interview interview) { + return AddInterviewCommand.COMMAND_WORD + " " + getInterviewDetails(interview); + } + + /** + * Returns the part of command string for the given {@code interview}'s details. + */ + public static String getInterviewDetails(Interview interview) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_POSITION + interview.getPosition().getTitle().fullTitle + " "); + for (int i = 0; i < interview.getCandidates().size(); i++) { + sb.append(PREFIX_CANDIDATE_INDEX + "" + (i + 1) + " "); + } + sb.append(PREFIX_DATE + interview.getDateInFormattedString() + " "); + sb.append(PREFIX_TIME + interview.getTimeInFormattedString() + " "); + sb.append(PREFIX_DURATION + interview.getDurationInFormattedString() + " "); + sb.append(PREFIX_INTERVIEW_STATUS + interview.getStatus().toString() + " "); + return sb.toString(); + } +} diff --git a/src/test/java/seedu/address/testutil/ModelStub.java b/src/test/java/seedu/address/testutil/ModelStub.java new file mode 100644 index 00000000000..201a3e918cb --- /dev/null +++ b/src/test/java/seedu/address/testutil/ModelStub.java @@ -0,0 +1,194 @@ +package seedu.address.testutil; + +import java.nio.file.Path; +import java.util.function.Predicate; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.GuiSettings; +import seedu.address.commons.core.index.Index; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyHrManager; +import seedu.address.model.ReadOnlyUserPrefs; +import seedu.address.model.interview.Interview; +import seedu.address.model.person.Person; +import seedu.address.model.position.Position; + +/** + * A default model stub that have all of the methods failing. + */ +public class ModelStub implements Model { + + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + throw new AssertionError("This method should not be called."); + } + + @Override + public GuiSettings getGuiSettings() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getHrManagerCandidatesFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getHrManagerPositionsFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public Path getHrManagerInterviewsFilePath() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setHrManagerCandidatesFilePath(Path hrManagerCandidatesFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setHrManagerPositionsFilePath(Path hrManagerPositionsFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setHrManagerInterviewsFilePath(Path hrManagerInterviewsFilePath) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addPerson(Person person) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setHrManager(ReadOnlyHrManager newData) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ReadOnlyHrManager getHrManager() { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasPerson(Person person) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasPosition(Position position) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Position getPositionReference(Position position) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deletePosition(Position target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void addPosition(Position position) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setPosition(Position target, Position editedPosition) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList<Position> getFilteredPositionList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredPositionList(Predicate<Position> predicate) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deletePerson(Person target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setPerson(Person target, Person editedPerson) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList<Person> getFilteredPersonList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredPersonList(Predicate<Person> predicate) { + throw new AssertionError("This method should not be called."); + } + + public void deletePositionFromPerson(Position p) { + throw new AssertionError("This method should not be called."); + } + + public boolean isPositionClosed(Position p) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasInterview(Interview interview) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void deleteInterview(Interview target) { + throw new AssertionError("This method should not be called."); + } + + @Override + public Person getPerson(Index index) { + throw new AssertionError("This method should not be called."); + } + public void addInterview(Interview interview) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setInterview(Interview target, Interview editedInterview) { + throw new AssertionError("This method should not be called."); + } + + @Override + public ObservableList<Interview> getFilteredInterviewList() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void updateFilteredInterviewList(Predicate<Interview> predicate) { + throw new AssertionError("This method should not be called."); + } + + public void deleteInterviewFromPerson(Interview i) { + throw new AssertionError("This method should not be called."); + } + + public void deletePersonFromInterview(Person p) { + throw new AssertionError("This method should not be called."); + } +} diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java index 6be381d39ba..d8691fc8a09 100644 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ b/src/test/java/seedu/address/testutil/PersonBuilder.java @@ -8,6 +8,9 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; +import seedu.address.model.person.Status; +import seedu.address.model.position.Position; import seedu.address.model.tag.Tag; import seedu.address.model.util.SampleDataUtil; @@ -20,12 +23,18 @@ public class PersonBuilder { public static final String DEFAULT_PHONE = "85355255"; public static final String DEFAULT_EMAIL = "amy@gmail.com"; public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; + public static final String DEFAULT_REMARK = "She likes aardvarks."; + public static final String DEFAULT_STATUS = "Applied"; + public static final Set<Position> DEFAULT_POSITION = SampleDataUtil.getPositionSet("HR Manager"); private Name name; private Phone phone; private Email email; private Address address; + private Remark remark; private Set<Tag> tags; + private Status status; + private Set<Position> positions; /** * Creates a {@code PersonBuilder} with the default details. @@ -35,7 +44,10 @@ public PersonBuilder() { phone = new Phone(DEFAULT_PHONE); email = new Email(DEFAULT_EMAIL); address = new Address(DEFAULT_ADDRESS); + remark = new Remark(""); tags = new HashSet<>(); + status = Status.APPLIED; + positions = DEFAULT_POSITION; } /** @@ -46,7 +58,10 @@ public PersonBuilder(Person personToCopy) { phone = personToCopy.getPhone(); email = personToCopy.getEmail(); address = personToCopy.getAddress(); + remark = personToCopy.getRemark(); tags = new HashSet<>(personToCopy.getTags()); + status = personToCopy.getStatus(); + positions = new HashSet<>(personToCopy.getPositions()); } /** @@ -89,8 +104,32 @@ public PersonBuilder withEmail(String email) { return this; } + /** + * Sets the {@code Remark} of the {@code Person} that we are building. + */ + public PersonBuilder withRemark(String remark) { + this.remark = new Remark(remark); + return this; + } + + /** + * Sets the {@code Status} of the {@code Person} that we are building. + */ + public PersonBuilder withStatus(String status) { + this.status = Status.parseStatus(status); + return this; + } + + /** + * Parses the {@code positions} into a {@code Set<Position>} and set it to the {@code Person} that we are building. + */ + public PersonBuilder withPositions(String ... positions) { + this.positions = SampleDataUtil.getPositionSet(positions); + return this; + } + public Person build() { - return new Person(name, phone, email, address, tags); + return new Person(name, phone, email, address, remark, tags, status, positions); } } diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java index 90849945183..c1a1399c092 100644 --- a/src/test/java/seedu/address/testutil/PersonUtil.java +++ b/src/test/java/seedu/address/testutil/PersonUtil.java @@ -4,13 +4,16 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Set; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.candidate.AddCandidateCommand; +import seedu.address.logic.candidate.EditCandidateCommand.EditPersonDescriptor; import seedu.address.model.person.Person; +import seedu.address.model.position.Position; import seedu.address.model.tag.Tag; /** @@ -21,8 +24,8 @@ public class PersonUtil { /** * Returns an add command string for adding the {@code person}. */ - public static String getAddCommand(Person person) { - return AddCommand.COMMAND_WORD + " " + getPersonDetails(person); + public static String getAddCandidateCommand(Person person) { + return AddCandidateCommand.COMMAND_WORD + " " + getPersonDetails(person); } /** @@ -34,9 +37,9 @@ public static String getPersonDetails(Person person) { sb.append(PREFIX_PHONE + person.getPhone().value + " "); sb.append(PREFIX_EMAIL + person.getEmail().value + " "); sb.append(PREFIX_ADDRESS + person.getAddress().value + " "); - person.getTags().stream().forEach( - s -> sb.append(PREFIX_TAG + s.tagName + " ") - ); + person.getTags().stream().forEach(s -> sb.append(PREFIX_TAG + s.tagName + " ")); + person.getPositions().stream().forEach(s -> sb.append(PREFIX_POSITION + s.getTitle().fullTitle + " ")); + sb.append(PREFIX_STATUS + person.getStatus().toString() + " "); return sb.toString(); } @@ -52,11 +55,20 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip if (descriptor.getTags().isPresent()) { Set<Tag> tags = descriptor.getTags().get(); if (tags.isEmpty()) { - sb.append(PREFIX_TAG); + sb.append(PREFIX_TAG + " "); } else { tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" ")); } } + if (descriptor.getPositions().isPresent()) { + Set<Position> positions = descriptor.getPositions().get(); + if (positions.isEmpty()) { + sb.append(PREFIX_POSITION + " "); + } else { + positions.forEach(s -> sb.append(PREFIX_POSITION).append(s.getTitle().fullTitle).append(" ")); + } + } + descriptor.getStatus().ifPresent(status -> sb.append(PREFIX_STATUS).append(status).append(" ")); return sb.toString(); } } diff --git a/src/test/java/seedu/address/testutil/PositionBuilder.java b/src/test/java/seedu/address/testutil/PositionBuilder.java new file mode 100644 index 00000000000..417a1abd67f --- /dev/null +++ b/src/test/java/seedu/address/testutil/PositionBuilder.java @@ -0,0 +1,53 @@ +package seedu.address.testutil; + +import seedu.address.model.position.Position; +import seedu.address.model.position.Position.PositionStatus; +import seedu.address.model.position.Title; + +/** + * A utility class to help with building Person objects. + */ +public class PositionBuilder { + public static final String DEFAULT_TITLE = "Sales Representative"; + + private Title title; + + private PositionStatus status; + + /** + * Creates a {@code PositionBuilder} with the default details. + */ + public PositionBuilder() { + title = new Title(DEFAULT_TITLE); + status = PositionStatus.OPEN; + } + + /** + * Initializes the PositionBuilder with the data of {@code positionToCopy}. + */ + public PositionBuilder(Position positionToCopy) { + title = positionToCopy.getTitle(); + status = positionToCopy.getStatus(); + } + + /** + * Sets the {@code Name} of the {@code Position} that we are building. + */ + public PositionBuilder withTitle(String title) { + this.title = new Title(title); + return this; + } + + /** + * Sets the {@code Name} of the {@code Position} that we are building. + */ + public PositionBuilder withStatus(PositionStatus status) { + this.status = status; + return this; + } + + public Position build() { + return new Position(title, status); + } + +} diff --git a/src/test/java/seedu/address/testutil/PositionUtil.java b/src/test/java/seedu/address/testutil/PositionUtil.java new file mode 100644 index 00000000000..aea2b4c557a --- /dev/null +++ b/src/test/java/seedu/address/testutil/PositionUtil.java @@ -0,0 +1,39 @@ +package seedu.address.testutil; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_POSITION_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import seedu.address.logic.position.AddPositionCommand; +import seedu.address.logic.position.EditPositionCommand.EditPositionDescriptor; +import seedu.address.model.position.Position; + +public class PositionUtil { + + /** + * Returns an add command string for adding the {@code position}. + */ + public static String getAddPositionCommand(Position position) { + return AddPositionCommand.COMMAND_WORD + " " + getPositionDetails(position); + } + + /** + * Returns the part of command string for the given {@code person}'s details. + */ + public static String getPositionDetails(Position position) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_TITLE + position.getTitle().fullTitle + " "); + return sb.toString(); + } + + /** + * Returns the part of command string for the given {@code EditPositionDescriptor}'s details. + */ + public static String getEditPositionDescriptorDetails(EditPositionDescriptor descriptor) { + StringBuilder sb = new StringBuilder(); + descriptor.getTitle().ifPresent(title -> sb.append(PREFIX_TITLE).append(title.fullTitle).append(" ")); + descriptor.getPositionStatus() + .ifPresent(status -> sb.append(PREFIX_POSITION_STATUS) + .append(status.toString().toLowerCase()).append(" ")); + return sb.toString(); + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java index 1e613937657..a486ba17d1e 100644 --- a/src/test/java/seedu/address/testutil/TypicalIndexes.java +++ b/src/test/java/seedu/address/testutil/TypicalIndexes.java @@ -9,4 +9,10 @@ public class TypicalIndexes { public static final Index INDEX_FIRST_PERSON = Index.fromOneBased(1); public static final Index INDEX_SECOND_PERSON = Index.fromOneBased(2); public static final Index INDEX_THIRD_PERSON = Index.fromOneBased(3); + public static final Index INDEX_FIRST_POSITION = Index.fromOneBased(1); + public static final Index INDEX_SECOND_POSITION = Index.fromOneBased(2); + public static final Index INDEX_THIRD_POSITION = Index.fromOneBased(3); + public static final Index INDEX_FIRST_INTERVIEW = Index.fromOneBased(1); + public static final Index INDEX_SECOND_INTERVIEW = Index.fromOneBased(2); + public static final Index INDEX_THIRD_INTERVIEW = Index.fromOneBased(3); } diff --git a/src/test/java/seedu/address/testutil/TypicalInterviews.java b/src/test/java/seedu/address/testutil/TypicalInterviews.java new file mode 100644 index 00000000000..d57d151d252 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalInterviews.java @@ -0,0 +1,45 @@ +package seedu.address.testutil; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import seedu.address.model.interview.Interview; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; + +/** + * A utility class containing a list of {@code Interview} objects to be used in tests. + */ +public class TypicalInterviews { + public static final Interview ASSISTANT_INTERVIEW = new InterviewBuilder() + .withPosition(TypicalPositions.ADMIN_ASSISTANT) + .build(); + + public static final Interview BOOKKEEPER_INTERVIEW = new InterviewBuilder() + .withPosition(TypicalPositions.BOOKKEEPER) + .build(); + public static final Interview HR_MANAGER_INTERVIEW = new InterviewBuilder() + .withPosition(TypicalPositions.HR_MANAGER) + .build(); + + public static final Interview ACCOUNTANT_INTERVIEW = new InterviewBuilder().withPosition(new Position(new Title( + "Accountant"))) + .withCandidates(new HashSet<>()).withDate(LocalDate.of(2021, 10, 15)) + .withStartTime(LocalTime.of(14, 0)).withDuration(Duration.ofMinutes(120)) + .withStatus(Interview.InterviewStatus.PENDING).build(); + + public static final Interview BOOKKEEPER_INTERVIEW_2 = new InterviewBuilder().withPosition(new Position(new Title( + "Bookkeeper"))) + .withCandidates(new HashSet<>()).withDate(LocalDate.of(2021, 12, 15)) + .withStartTime(LocalTime.of(14, 0)).withDuration(Duration.ofMinutes(120)) + .withStatus(Interview.InterviewStatus.PENDING).build(); + + public static List<Interview> getTypicalInterviews() { + return new ArrayList<>(Arrays.asList(ASSISTANT_INTERVIEW, BOOKKEEPER_INTERVIEW, HR_MANAGER_INTERVIEW)); + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java index fec76fb7129..c7cd12024b3 100644 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ b/src/test/java/seedu/address/testutil/TypicalPersons.java @@ -1,76 +1,137 @@ package seedu.address.testutil; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_ADDRESS_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_STATUS_APPLIED; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.candidate.CommandTestUtil.VALID_TAG_HUSBAND; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; -import seedu.address.model.AddressBook; +import seedu.address.model.HrManager; +import seedu.address.model.interview.Interview; import seedu.address.model.person.Person; +import seedu.address.model.position.Position; +import seedu.address.model.position.Title; /** * A utility class containing a list of {@code Person} objects to be used in tests. */ public class TypicalPersons { - public static final Person ALICE = new PersonBuilder().withName("Alice Pauline") + public static final Person ALICE = new PersonBuilder().withName("Alice Pauline").withPositions("Accountant") .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") - .withPhone("94351253") - .withTags("friends").build(); - public static final Person BENSON = new PersonBuilder().withName("Benson Meier") + .withPhone("94351253").withRemark("She likes aardvarks.") + .withTags("friends").withStatus("Applied").build(); + public static final Person BENSON = new PersonBuilder().withName("Benson Meier").withPositions("HR Manager") .withAddress("311, Clementi Ave 2, #02-25") - .withEmail("johnd@example.com").withPhone("98765432") - .withTags("owesMoney", "friends").build(); + .withEmail("johnd@example.com").withPhone("98765432").withRemark("He can't handle beer!") + .withTags("owesMoney", "friends").withStatus("Scheduled").build(); public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") - .withEmail("heinz@example.com").withAddress("wall street").build(); + .withEmail("heinz@example.com").withAddress("wall street").withStatus("Rejected").build(); public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") - .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build(); + .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends") + .withStatus("Scheduled").build(); public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") - .withEmail("werner@example.com").withAddress("michegan ave").build(); + .withEmail("werner@example.com").withAddress("michegan ave").withStatus("Withdrawn").build(); public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") - .withEmail("lydia@example.com").withAddress("little tokyo").build(); + .withEmail("lydia@example.com").withAddress("little tokyo").withStatus("Applied").build(); public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442") - .withEmail("anna@example.com").withAddress("4th street").build(); + .withEmail("anna@example.com").withAddress("4th street").withStatus("Scheduled").build(); // Manually added public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") - .withEmail("stefan@example.com").withAddress("little india").build(); + .withEmail("stefan@example.com").withAddress("little india").withStatus("Interviewed") + .withPositions("Bookkeeper").build(); public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") - .withEmail("hans@example.com").withAddress("chicago ave").build(); + .withEmail("hans@example.com").withAddress("chicago ave").withStatus("Applied").withPositions("Bookkeeper") + .build(); + + public static final Person JOHN = new PersonBuilder().withName("John Doe").withPhone("98780121") + .withEmail("johnDoe@example.com").withAddress("chicago").withStatus("Scheduled").withPositions("Clerk", + "Bookkeeper").build(); + // Manually added - Person's details found in {@code CommandTestUtil} public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) - .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND) + .withStatus(VALID_STATUS_APPLIED).build(); public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) - .build(); + .withStatus(VALID_STATUS_APPLIED).build(); public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER - private TypicalPersons() {} // prevents instantiation + private TypicalPersons() { + } // prevents instantiation /** - * Returns an {@code AddressBook} with all the typical persons. + * Returns an {@code HrManager} with all the typical persons. */ - public static AddressBook getTypicalAddressBook() { - AddressBook ab = new AddressBook(); + public static HrManager getTypicalHrManager() { + HrManager ab = new HrManager(); for (Person person : getTypicalPersons()) { ab.addPerson(person); } + + for (Position position : getTypicalPositions()) { + ab.addPosition(position); + } + + for (Interview interview : getTypicalInterviews()) { + ab.addInterview(interview); + } + return ab; } + public static HrManager getTypicalHrManagerWithOnlyTypicalPersons() { + HrManager hr = new HrManager(); + for (Person person : getTypicalPersons()) { + hr.addPerson(person); + } + return hr; + } + + public static HrManager getTypicalHrManagerWithOnlyTypicalInterviews() { + HrManager hr = new HrManager(); + for (Interview interview : getTypicalInterviews()) { + hr.addInterview(interview); + } + return hr; + } + public static List<Person> getTypicalPersons() { return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); } + + public static List<Position> getTypicalPositions() { + return new ArrayList<>(Arrays.asList(TypicalPositions.ADMIN_ASSISTANT, TypicalPositions.BOOKKEEPER, + TypicalPositions.HR_MANAGER, TypicalPositions.ACCOUNTANT, TypicalPositions.CLOSED_POSITION_CLERK)); + } + + //this follows typicalInterviewHrManager.json data + public static List<Interview> getTypicalInterviews() { + Interview firstEntry = new InterviewBuilder().withPosition(new Position(new Title("Accountant"))) + .withCandidates(new HashSet<>()).withDate(LocalDate.of(2021, 10, 15)) + .withStartTime(LocalTime.of(14, 0)).withDuration(Duration.ofMinutes(120)) + .withStatus(Interview.InterviewStatus.PENDING).build(); + Interview secondEntry = new InterviewBuilder().withPosition(new Position(new Title("Bookkeeper"))) + .withCandidates(new HashSet<>()).withDate(LocalDate.of(2021, 12, 15)) + .withStartTime(LocalTime.of(14, 0)).withDuration(Duration.ofMinutes(120)) + .withStatus(Interview.InterviewStatus.PENDING).build(); + return new ArrayList<>(Arrays.asList(firstEntry, secondEntry)); + } } diff --git a/src/test/java/seedu/address/testutil/TypicalPositions.java b/src/test/java/seedu/address/testutil/TypicalPositions.java new file mode 100644 index 00000000000..73b0d83a624 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalPositions.java @@ -0,0 +1,20 @@ +package seedu.address.testutil; + +import seedu.address.model.position.Position; + +public class TypicalPositions { + public static final Position ADMIN_ASSISTANT = new PositionBuilder().withTitle("Administrative Assistant") + .build(); + + public static final Position BOOKKEEPER = new PositionBuilder().withTitle("Bookkeeper") + .build(); + + public static final Position HR_MANAGER = new PositionBuilder().withTitle("HR Manager") + .build(); + + public static final Position ACCOUNTANT = new PositionBuilder().withTitle("Accountant") + .build(); + + public static final Position CLOSED_POSITION_CLERK = new PositionBuilder().withTitle("Clerk") + .withStatus(Position.PositionStatus.CLOSED).build(); +}