diff --git a/README.md b/README.md
index 13f5c77403f..f9d49d1407a 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,10 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
+# TuitionConnect
+
+[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S1-CS2103T-F10-4/tp/actions)
![Ui](docs/images/Ui.png)
+### Description
+TuitionConnect is a powerful **CLI** application designed to streamline the management of your tutoring business for **private tutors**. TuitionConnect helps to effortlessly *manage students*, *schedules* and *progress tracking* while ensuring *financial organization*.
-* 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.
+### Credits
+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.
diff --git a/build.gradle b/build.gradle
index a2951cc709e..983434d96e3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -66,7 +66,11 @@ dependencies {
}
shadowJar {
- archiveFileName = 'addressbook.jar'
+ archiveFileName = 'tuitionconnect.jar'
+}
+
+run {
+ enableAssertions = true
}
defaultTasks 'clean', 'test'
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index eb761a9b9a7..ad5afc6f59a 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -16,7 +16,7 @@
-
+
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..d145e004652 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -9,51 +9,49 @@ You can reach us at the email `seer[at]comp.nus.edu.sg`
## Project team
-### John Doe
+### Lang Heran
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/heran9)] [[portfolio](team/heran9.md)]
-* Role: Project Advisor
-
-### Jane Doe
+* Role: Developer
+* Responsibilities: Feature development, Documentation, Testing
-
+### Lim Yih Fei
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/yihfei)]
+[[portfolio](team/yihfei.md)]
-* Role: Team Lead
-* Responsibilities: UI
+* Role: Developer
+* Responsibilities: Quality Assurance, UI
-### Johnny Doe
+### Lam Jin Heng Braydon
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](https://github.com/lambraydon)] [[portfolio](team/lambraydon.md)]
* Role: Developer
-* Responsibilities: Data
+* Responsibilities: Feature Development, Quality Assurance, UI
-### Jean Doe
+### Armando Jovan Kusuma
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/jovkusuma)]
+[[portfolio](team/jovkusuma.md)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Responsibilities: Feature Development, Quality Assurance, Testing
-### James Doe
+### Winston Leonard Prayonggo
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/WinstonLeonard)]
+[[portfolio](team/winstonleonard.md)]
* Role: Developer
-* Responsibilities: UI
+* Responsibilities: Refactoring code, Feature Development, Testing
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 8a861859bfd..52efb001a91 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -2,14 +2,72 @@
layout: page
title: Developer Guide
---
-* Table of Contents
-{:toc}
+## Table of Contents
+
+ * [**Acknowledgements**](#acknowledgements)
+ * [**Setting up, getting started**](#setting-up-getting-started)
+ * [**Design**](#design)
+ * [Architecture](#architecture)
+ * [UI component](#ui-component)
+ * [Logic component](#logic-component)
+ * [Model component](#model-component)
+ * [Storage component](#storage-component)
+ * [Common classes](#common-classes)
+ * [**Implementation**](#implementation)
+ * [Add feature](#add-feature)
+ * [Design considerations:](#design-considerations)
+ * [List feature](#list-feature)
+ * [Design considerations:](#design-considerations-1)
+ * [Find feature](#find-feature)
+ * [Design considerations:](#design-considerations-2)
+ * [Edit feature](#edit-feature)
+ * [Design considerations:](#design-considerations-3)
+ * [Find Free Time feature](#find-free-time-feature)
+ * [Design Considerations](#design-considerations-4)
+ * [Calculate monthly revenue](#calculate-monthly-revenue)
+ * [Design Considerations](#design-considerations-5)
+ * [Undo/redo feature](#undoredo-feature)
+ * [Design considerations:](#design-considerations-6)
+ * [Mark paid/unpaid features](#mark-paidunpaid-features)
+ * [Design considerations:](#design-considerations-7)
+ * [**Documentation, logging, testing, configuration, dev-ops**](#documentation-logging-testing-configuration-dev-ops)
+ * [**Appendix: Requirements**](#appendix-requirements)
+ * [Product scope](#product-scope)
+ * [User stories](#user-stories)
+ * [Use cases](#use-cases)
+ * [Non-Functional Requirements](#non-functional-requirements)
+ * [Glossary](#glossary)
+ * [**Appendix: Instructions for manual testing**](#appendix-instructions-for-manual-testing)
+ * [Launch and shutdown](#launch-and-shutdown)
+ * [List tutee(s)](#list-tutees)
+ * [Adding a tutee](#adding-a-tutee)
+ * [Deleting a tutee](#deleting-a-tutee)
+ * [Finding a tutee](#finding-a-tutee)
+ * [Editing a tutee](#editing-a-tutee)
+ * [Find free time](#find-free-time)
+ * [Marking a tutee as paid](#marking-a-tutee-as-paid)
+ * [Marking a tutee as not paid](#marking-a-tutee-as-not-paid)
+ * [Listing all unpaid tutees](#listing-all-unpaid-tutees)
+ * [Undo command](#undo-command)
+ * [Redo command](#redo-command)
+ * [Manually editing data file](#manually-editing-data-file)
+ * [**Planned Enhancements**](#planned-enhancements)
+ * [Batch Processing for Paid Command](#batch-processing-for-paid-command)
+ * [Scheduled Unpaid Marking](#scheduled-unpaid-marking)
+ * [Find using multiple keywords](#find-using-multiple-keywords)
+ * [Maximum PayRate](#maximum-payrate)
+ * [Prevent Commands meant to Modify Tutee Data from Not Changing the Data](#prevent-commands-meant-to-modify-tutee-data-from-not-changing-the-data)
+ * [Enable Group Lessons](#enable-group-lessons)
+ * [Enhance Edit Feature](#enhance-edit-feature)
+
+
--------------------------------------------------------------------------------------------------------------------
## **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 adapted from **[AddressBook 3(AB3)](https://github.com/nus-cs2103-AY2324S1/tp)**
+* Undo and Redo features are adapted from proposed implementations from **[AddressBook 3(AB3)](https://github.com/nus-cs2103-AY2324S1/tp)**
--------------------------------------------------------------------------------------------------------------------
@@ -36,7 +94,7 @@ Given below is a quick overview of main components and how they interact with ea
**Main components of the architecture**
-**`Main`** (consisting of classes [`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)) is in charge of the app launch and shut down.
+**`Main`** (consisting of classes [`Main`](https://github.com/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down.
* At app launch, it initializes the other components in the correct sequence, and connects them up with each other.
* At shut down, it shuts down the other components and invokes cleanup methods where necessary.
@@ -68,13 +126,13 @@ 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/AY2324S1-CS2103T-F10-4/tp/blob/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 consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `ScheduleListPanel`, `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/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/resources/view/MainWindow.fxml)
The `UI` component,
@@ -85,7 +143,7 @@ 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/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/logic/Logic.java)
Here's a (partial) class diagram of the `Logic` component:
@@ -114,33 +172,27 @@ How the parsing works:
* 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.
### 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/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/model/Model.java)
The `Model` component,
-* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object).
+* stores the tutee 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 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/AY2324S1-CS2103T-F10-4/tp/blob/master/src/main/java/seedu/address/storage/Storage.java)
The `Storage` component,
-* can save both address book data and user preference data in JSON format, and read them back into corresponding objects.
+* can save both Tuition Connect 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`)
@@ -154,37 +206,236 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### Add feature
+The `AddCommand` extends the `Command` class. While mostly similar to `delete` illustrated above, the command contains
+checks to prevent any duplicate `Person` object (i.e. same name and phone number) as well as clashes in schedules.
+If it passes these checks, the person is added into the system.
+
+`AddCommand` takes in the following fields:
+* **Name (Compulsory field)**: String composed of character between A-Z and a-z.
+* **Phone number (Compulsory field)**: Any number at least 3 digits long.
+* **Email (Compulsory field)** String with restrictions in characters (XXXXXXXX@emaildomain)
+* **Address (Compulsory field)**: String without restriction in characters.
+* **Subject (Compulsory field)**: String without restriction in characters.
+* **Day (Compulsory field)**: String with restrictions in characters, non-case sensitive (Mon/Monday/Tue/Tuesday/Wed/Wednesday/Thu/Thursday/Fri/Friday/Sat/Saturday/Sun/Sunday).
+* **Begin (Compulsory field)**: String with restrictions (HHMM).
+* **End (Compulsory field)**: String with restrictions (HHMM).
+* **PayRate (Compulsory field)**: String with restrictions in characters, only numbers allowed (no negative numbers)
+
+The following sequence diagram shows how the add command works.
+
+![AddSequenceDiagram](images/AddSequenceDiagram.png)
+
+The following activity diagram summarizes what happens when a user executes a new command:
+
+![AddActivityDiagram](images/AddActivityDiagram.png)
+
+#### Design considerations:
+
+**Aspect: How add executes:**
+
+* **Alternative 1 (current choice):** All fields must be included in a single command input.
+ * Pros: Easy to implement.
+ * Cons: Command input may be too long and less user-friendly.
+
+* **Alternative 2**: Allow for optional parameters with default values, with the tutee's name and phone being the compulsory ones.
+ * Pros: More user-friendly, command will not be too lengthy.
+ * Cons: Harder to implement.
+
+### List feature
+
+There are three commands that deal with listing tutees:
+
+1. `ListCommand` - Shows the current list of all tutees in the list
+2. `ListByDayCommand` - Shows the current list of tutees who have lessons on a specified day
+3. `ListUnPaidCommand` - Shows the current list of tutees who have not paid
+
+The `ListCommand` extends the `Command` class. Both the `ListByDayCommand` and the `ListUnPaidCommand` extend the `ListCommand` class. All three commands override `Command#execute`.
+The `ListCommandParser` is responsible for returning the appropriate `ListCommand` based on the command format.
+
+
+
+The `ListByDayCommand` is initialised with a `DayPredicate` and updates the `FilteredPersonList` to only display Persons whose `Day` field matches the specified input.
+
+The following sequence diagram shows how the list by day command works.
+
+![ListByDaySequenceDiagram](images/ListByDaySequenceDiagram.png)
+
+The `ListUnPaidCommand` follows a similar implementation to `ListByDayCommand`. It is initialised with a `PaidPredicate` instead and updates
+the `FilteredPersonList` to only display Persons whose `isPaid` field is false.
+
+#### Design considerations:
+
+**Aspect: How to implement `ListByDayCommand` and `ListUnPaidCommand`:**
+
+* **Alternative 1 (current choice):** Extend the `ListCommand` class.
+ * Pros: Greater use of OOP.
+ * Cons: Harder to implement.
+
+* **Alternative 2:** Individual command class without extending `ListCommand`.
+ * Pros: Easier to implement.
+ * Cons: Less abstraction.
+
+### Find feature
+The `FindCommand` extends the `Command` class. It allows the user to find for tutees by specifying their names and/or
+subject using their prefixes. Both parameters are optional, but at least one of them must be specified for the `find`
+command to work properly.
+
+`NameContainsKeywordsPredicate` is a class which takes a list of strings as input, and is used to test whether the input
+matches any of the names inside the tutee list.
+
+`SubjectContainsKeywordsPredicate` is a class which takes a list of string as input, and is used to test whether the input
+matches any of the subjects inside the tutee list.
-#### Proposed Implementation
+As for `NameSubjectPredicate`, it takes in two parameters `NameContainsKeywordsPredicate` and
+`SubjectContainsKeywordsPredicate` as to accommodate for both input of fields n/ and sb/.
-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:
+However, since the method `updateFilteredPersonList` can only take one parameter, the merging of both
+`NameContainsKeywordsPredicate` and `SubjectContainsKeywordsPredicate` into `NameSubjectPredicate` is the implementation
+we decided to go with.
-* `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.
+`FindCommand` takes in the following fields:
+* **Name (Optional field)**: String composed of character between A-Z and a-z.
+* **Subject (Optional field)**: String without restriction in characters.
+
+The following sequence diagram shows how the edit command works.
+![FindSequenceDiagram](images/FindSequenceDiagram.png)
+
+#### Design considerations:
+
+**Aspect: How find executes:**
+
+* **Alternative 1 (current choice):** Find tutees based on inputs for prefixes n/ and sb/.
+ * Pros: More sophisticated as it can search for subjects rather than only for name.
+ * Cons: Less user-friendly for beginners as it requires an extra step of inputting prefixes.
+* **Alternative 2:** Find tutees based on their name.
+ * Pros: More user-friendly as the command format would only be `find [name]`.
+ * Cons: Users cannot search for tutees by subject.
+
+### Edit feature
+The `EditCommand` extends the `Command` class. It allows the user to edit fields of the tutee by specifying the index
+of the tutee. The command contains checks to prevent any duplicate `Person` object (i.e. same name and phone number)
+as well as clashes in schedules. If it passes these checks, the person is edited successfully.
+
+`EditCommand` takes in the following fields:
+* **Index (Compulsory Field)**: Numbers between 1 to the number of people inside the list.
+* **Name (Optional field)**: String composed of character between A-Z and a-z.
+* **Phone number (Optional field)**: Any number at least 3 digits long.
+* **Email (Optional field)** String with restrictions in characters (XXXXXXXX@emaildomain)
+* **Address (Optional field)**: String without restriction in characters.
+* **Subject (Optional field)**: String without restriction in characters.
+* **Day (Optional field)**: String with restrictions in characters, non-case sensitive (Mon/Monday/Tue/Tuesday/Wed/Wednesday/Thu/Thursday/Fri/Friday/Sat/Saturday/Sun/Sunday).
+* **Begin (Optional field)**: String with restrictions (HHMM).
+* **End (Optional field)**: String with restrictions (HHMM).
+* **PayRate (Optional field)**: String with restrictions in characters, only numbers allowed (no negative numbers)
+
+The following sequence diagram shows how the edit command works.
+![EditSequenceDiagram](images/EditSequenceDiagram.png)
+
+The following activity diagram summarizes what happens when a user executes an edit command:
+![EditActivityDiagram](images/EditActivityDiagram.png)
+
+#### Design considerations:
+
+**Aspect: How edit executes:**
+
+* **Alternative 1 (current choice):** User specify which fields to edit by their prefixes.
+ * Pros: User can edit the fields that require changes by specifying their prefix.
+ * Cons: Command input may be too long and less user-friendly.
+* **Alternative 2:** Users cannot edit tutees that are already added, and can only do delete and re-adding
+ of tutees whenever changes are necessary.
+ * Pros: Less prone to bugs, and is simpler for developers to implement.
+ * Cons: Not user-friendly and takes multiple steps for the user.
+
+### Find Free Time feature
+
+The `freeTime` Command extends the `Command` class.
+
+`freeTime` takes in the following fields:
+* **Day (Compulsory field)**: String with restrictions in characters, non-case sensitive (Mon/MondayTue/Tuesday/Wed/Wednesday/Thu/Thursday/Fri/Friday/Sat/Saturday/Sun/Sunday).
+* **Duration (Compulsory field)**: Positive Integer to represent duration in **minutes**.
+* **Begin (Compulsory field)**: String with restrictions (HHMM).
+* **End (Compulsory field)**: String with restrictions (HHMM).
+
+It displays a list of timeslots where the user is free on that _Day_, starting from _Begin_ to _End_. The timeslots listed down
+must also be greater than the duration provided.
+
+The following sequence diagram shows how the `freeTime` command works.
+![FreeTimeSequenceDiagram](images/FreeTimeSequenceDiagram.png)
+
+Variables inside the sequence diagram:
+* toFind, Interval: Both are instances of the `Interval` Class which encapsulates the `Day`, `Duration`, `Begin`, `End` fields
+* results: A list of strings where the user is busy on the specified given `Interval` class.
+* timeslots: A list of timeslots after parsing the list of strings.
+* availableTime: A list of timeslots where the user is free.
+
+The following activity diagram summarizes what happens when a user executes a `freeTime` command:
+![FreeTimeActivityDiagram](images/FreeTimeActivityDiagram.png)
+
+#### Design Considerations
+**Aspect: How `freeTime` executes:**
+
+* **Alternative 1 (current choice):** The command first finds timeslots when the user is busy on that _Day_ by looking at the tutees' schedules inside the
+ `UniquePersonList`. The TimeSlot Class finds free time based on the list of
+ timeslots when the user is busy, and then returns a list of timeslots where the user is free. (Each timeslot is between _Begin_ and _End_,
+ and is at least _Duration_ long)
+ * Pros: Command is short and simple to use.
+ * Cons: During the first round of user-testing, some new users were confused on how to use the command.
+
+
+### Calculate monthly revenue
+
+
+The `RevenueCommand` extends the `command class`. The command first gets a list containing all tutees.
+The total monthly revenue can be calculated now by iterating through the list and calling `Person#getMonthlyFee`.
+
+The total monthly revenue is calculated as such:
+*Total Monthly Revenue* = Sum of every tutee's `monthlyFee`
+
+The following sequence diagram shows how the `RevenueCommand` works:
+![RevenueSequenceDiagram.png](images/RevenueSequenceDiagram.png)
+
+#### Design Considerations
+**Aspect: How `monthlyFee` is calculated:**
+
+* **Alternative 1 (current choice):** Calculate `monthlyFee` only when executing `RevenueCommand`.
+ * Pros: Up-to-date revenue figure as `PayRate` value and number of lessons monthly may change over time.
+ * Cons: Potentially more method calls to generate same value.
+
+* **Alternative 2:** Calculate `monthlyFee` when instantiating `Person` and include it as a field in `Person`.
+ * Pros: Readily accessible `monthlyFee` value.
+ * Cons: Have to implement logic to update `monthlyFee` when `PayRate` value and number of lessons monthly changes.
+
+
+### Undo/redo feature
+
+The undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations:
+
+* `VersionedAddressBook#commit()` — Saves the current tutee list state in its history.
+* `VersionedAddressBook#undo()` — Restores the previous tutee list state from its history.
+* `VersionedAddressBook#redo()` — Restores a previously undone tutee list state from its history.
These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
-Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state.
+Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial tutee list state, and the `currentStatePointer` pointing to that single tutee list state.
![UndoRedoState0](images/UndoRedoState0.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.
+Step 2. The user executes `delete 5` command to delete the 5th person in the tutee list. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the tutee list after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted tutee list state.
![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`.
+Step 3. The user executes `add n/David …` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified tutee list state to be saved into the `addressBookStateList`.
![UndoRedoState2](images/UndoRedoState2.png)
-
: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`.
+
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the tutee list state will not be saved into the `addressBookStateList`.
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
+Step 4. The user now decides that adding the 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 tutee list state, and restores the tutee list to that state.
![UndoRedoState3](images/UndoRedoState3.png)
@@ -201,17 +452,17 @@ The following sequence diagram shows how the undo operation works:
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state.
+The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the tutee list to that state.
-
: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.
+
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest tutee list 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.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
+Step 5. The user then decides to execute the command `list`. Commands that do not modify the tutee list, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
![UndoRedoState4](images/UndoRedoState4.png)
-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.
+Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all tutee list 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.
![UndoRedoState5](images/UndoRedoState5.png)
@@ -221,22 +472,57 @@ The following activity diagram summarizes what happens when a user executes a ne
#### Design considerations:
-**Aspect: How undo & redo executes:**
+**Aspect: How undo & redo executes**
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+* **Alternative 1 (current choice):** Saves the entire tutee list.
+ * Pros: Easy to implement.
+ * Cons: May have performance issues in terms of memory usage.
* **Alternative 2:** Individual command knows how to undo/redo by
itself.
- * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
- * Cons: We must ensure that the implementation of each individual command are correct.
+ * 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.
-_{more aspects and alternatives to be added}_
+### Mark paid/unpaid features
+The proposed mark paid/check paid mechanism can check whether the person has paid or not by implementing a new boolean field 'paid' in the person object, it implements the following operations:
-### \[Proposed\] Data archiving
+* `paid [index]` — Mark the person at the index as paid.
+* `unpaid [index]` — Mark the person at the index as not paid.
+* `list unpaid` — List all the persons who haven't paid in the list.
+* `unpaidAll` — Reset all students' payment status to not paid.
+
+The following sequence diagram shows how paid command works:
+
+![PaidSequenceDiagram.png](images/PaidSequenceDiagram.png)
+
+The unpaid command works similar to the paid command.
+
+The following sequence diagram shows how unpaidAll command works:
+
+![UnpaidAllSequenceDiagram.png](images/UnpaidAllSequenceDiagram.png)
+
+#### Design considerations:
+
+**Aspect: The choice of paid data type:**
+
+* **Alternative 1 (current choice):** Use simple boolean value.
+ * Pros: Easy to implement, fits the requirement: two status (paid, not paid).
+ * Cons: Different from all other fields in the person class, hard to maintain.
+
+* **Alternative 2:** Create a new paid class.
+ * Pros: Fits the other fields in the class.
+ * Cons: Hard to implement, waste of source (such as code storage, might affect the efficiency of the code).
+
+**Aspect: How to implement mark paid features:**
+
+* **Alternative 1 (current choice):** Create a new person, set everything else the same as before, and set paid as true.
+ * Pros: Since we created a new person, the command works individually and not depends on the other commands.
+ * Cons: Hard to implement.
+
+* **Alternative 2:** Use the edit command to edit the paid status of the person.
+ * Pros: Easy to implement.
+ * Cons: The paid command will rely on the edit commands, which violates the principle to reduce correlation between classes.
-_{Explain here how the data archiving feature will be implemented}_
--------------------------------------------------------------------------------------------------------------------
@@ -255,74 +541,304 @@ _{Explain here how the data archiving feature will be implemented}_
### Product scope
-**Target user profile**:
+**Target user profile**: Private tutors not affiliated to any tuition organisations
-* has a need to manage a significant number of contacts
+* has a need to manage multiple tutees
+* has a need for managing personal tutoring schedule
* 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**: It is tedious for tutors to keep track of multiple students and this is done conventionally through calendar applications. Simplify tutoring business with TuitionConnect. Effortlessly manage students, schedules and progress tracking while ensuring financial organization in an all in one product at a faster rate than non CLI applications.
### 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… |
+|----------|---------|---------------------------------------------------------|-----------------------------------------------------------------|
+| `* * *` | tutor | view a list of all tutees | see whoever are my tutees that I teach |
+| `* * *` | tutor | view the specific details of a single tutee | see the different informations tailored to the tutee |
+| `* * *` | tutor | add a new tutee | keep track of my tutees that I teach |
+| `* * *` | tutor | find a tutee | search for a specific tutee that I teach |
+| `* * *` | tutor | edit their details | account for changes in their information e.g. change in address |
+| `* *` | tutor | remove tutees from the list | keep track of tutees that I have stopped teaching |
+| `* *` | tutor | mark students that have already paid | keep track of students' payment statuses |
+| `* *` | tutor | check all students who haven't paid | easily remind students who haven't paid |
+| `* *` | tutor | undo and redo commands I made in the application | easily revert any mistakes |
+| `* *` | tutor | calculate my total monthly revenue | better financially plan for my tutoring business |
+| `* *` | tutor | view a list tutees whose lessons fall on a specific day | be reminded if I have any classes on that particular day |
+| `* *` | tutor | find timeslots when I am free | prevent schedle clashes when I add or edit a tutee's schedule |
+
### Use cases
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+(For all use cases below, the **System** is the `TuitionConnect` and the **Actor** is the `user`, unless specified otherwise)
+
+**Use case: UC01 - List all tutees**
+
+**MSS**
+
+1. User requests to list all tutees.
+2. System shows all tutees.
+3. System displays the success message.
+
+ Use case ends.
+
+**Extensions**
+
+- 2a. The list of tutees is empty.
+ - 2a1. System informs the user that the list is empty.
-**Use case: Delete a person**
+ Use case ends.
+
+
+
+**Use case: UC02 - List tutees whose lessons are on a specified day**
**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 tutees whose lessons are on Monday.
+2. System shows all tutees whose lessons are on Monday.
Use case ends.
**Extensions**
-* 2a. The list is empty.
+- 2a. The list of tutees is empty.
+ - 2a1. System informs the user that the list is empty.
Use case ends.
+
+
+
+**Use case: UC03 - Add a tutee**
+
+**MSS**
+
+1. User requests to add a tutee.
+2. System adds a tutee.
+3. System displays the success message.
+
+ Use case ends.
+
+**Extensions**
+
+- 1a. User inputs incomplete tutee data.
+ - 1a1. System informs user of the incomplete tutee data.
+
+ Use case resumes at 1.
+
+- 1b. User inputs name and phone number that already exists in the tutee list.
+ - 1b1. System informs user of duplicate tutees.
+
+ Use case resumes at 1.
+
+- 1c. User inputs a clash in schedule.
+ - 1c1. System informs user of the clash in schedules.
+
+ Use case resumes at 1.
-* 3a. The given index is invalid.
+- 1d. User inputs begin time which is greater than the end time.
+ - 1d1. System informs that begin time must be smaller than the end time.
+
+ Use case resumes at 1.
+
+
+
+
+**Use case: UC04 - Delete a tutee**
+
+**MSS**
+
+1. User views the list of tutees.
+2. User requests to delete a tutee.
+3. System deletes the tutee.
+4. System displays the success message.
+
+ Use case ends.
+
+**Extensions**
+
+- 2a. The tutee that the user is trying to delete does not exist in the list.
+ - 2a1. System informs that user does not exist.
+
+
+
+**Use case: UC05 - Edit a tutee**
+
+**MSS**
+
+1. User views the list of tutees.
+2. User requests to edit a tutee.
+3. System edits the tutee.
+4. System displays the success message.
+
+ Use case ends.
+
+**Extensions**
+
+- 2a. The schedule of the edited tutee clashes with an existing schedule.
+ - 2a1. System informs that there is a clash in schedules.
+
+ Use case resumes at 2.
+
+- 2b. The edited begin time is after than the original end time.
+ - 2b1. System informs that begin time must be smaller than the end time.
+
+ Use case resumes at 2.
+
+- 2c. The edited end time is before the original begin time.
+ - System informs that begin time must be smaller than the end time.
+
+ Use case resumes at 2.
+
+- 2d. The edited begin time is after the edited begin time.
+ - 2d1. System informs that begin time must be smaller than the end time.
+
+ Use case resumes at 2.
+
+
+
+**Use case: UC06 - Find a tutee**
+
+**MSS**
+
+1. User requests to find a tutee.
+2. System finds the tutee.
+3. System displays the success message.
+
+**Extensions**
- * 3a1. AddressBook shows an error message.
+- 2a. The user inputs more than one word for name field.
+ - 2a1. System informs that name can only take one word.
- Use case resumes at step 2.
+ Use case resumes at 2.
-*{More to be added}*
+- 2b. The user inputs more than one word for subject field.
+ - 2b1. System informs that subject can only take one word.
+
+ Use case resumes at 2.
+
+
+
+**Use case: UC07 - Mark a tutee as paid**
+
+**MSS**
+
+1. User views the list of tutees.
+2. User requests mark the specific tutee as paid.
+3. System marks the tutee as paid.
+4. System displays the success message.
+
+ Use case ends.
+
+**Extensions**
+
+- 2a. The tutee that the user is trying to mark as paid does not exist in the list.
+ - 2a1. System informs that user does not exist.
+
+
+
+**Use case: UC08 - Reset all tutees in the list to not paid**
+
+**MSS**
+
+1. User views the list of tutees.
+2. User requests mark all the tutees in the current list as not paid.
+3. System marks all the tutee in the list as not paid.
+4. System displays the success message.
+
+ Use case ends.
+
+
+
+**Use case: UC09 - Undo a command**
+
+**MSS**
+1. User requests to undo.
+2. System updates the tutee data to the previous state.
+3. System informs user that the command is undone.
+
+ Use case ends.
+
+**Extensions**
+* 1a. No command to be undone.
+ * 1a1. System informs user that there is nothing to undo.
+
+ Use case ends.
+
+
+
+**Use case: UC10 - Redo a command**
+
+**MSS**
+1. User requests to redo.
+2. System updates the tutee data to the next state.
+3. System informs user that the command is redone.
+
+ Use case ends.
+
+**Extensions**
+* 1a. No command to be redone.
+ * 1a1. System informs user that there is nothing to redo.
+
+ Use case ends.
+
+
+
+**Use case: UC11 - Finding free time**
+
+**MSS**
+1. User requests to find free time
+2. System shows the list of available free time
+
+**Extensions**
+- 2a. The user does not have any free slots available.
+<<<<<<< HEAD
+ - 2a1. System informs user that there is no available timeslots.
+
+
+
+**Use case: UC12 - Get monthly revenue**
+
+**MSS**
+
+1. User requests for monthly revenue.
+2. System displays the monthly revenue figure.
+
+ Use case ends.
+
+
### Non-Functional Requirements
1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
-2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
-3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
-
-*{More to be added}*
+2. The software should be platform independent (i.e. work on the Windows, Linux, and OS-X platforms).
+3. Should be able to hold up to 1000 tutees without a noticeable sluggishness in performance for typical usage.
+4. 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.
+5. The product is a single user product (i.e. The data file created by one user cannot be accessed by another user during regular operations)
+6. The data should be stored locally and should be in a human editable text file.
+7. The product should not use a DBMS to store data.
+8. The software should work without requiring an installer.
+9. The software should not depend on a remote server.
+10. The GUI should work well (i.e., should not cause any resolution-related inconveniences to the user) for, standard screen resolutions 1920x1080 and higher, and, for screen scales 100% and 125%.
+11. the GUI should be usable (i.e., all functions can be used even if the user experience is not optimal) for, resolutions 1280x720 and higher, and, for screen scales 150%.
+12. The product should be packaged into a single JAR file.
### Glossary
-* **Mainstream OS**: Windows, Linux, Unix, OS-X
-* **Private contact detail**: A contact detail that is not meant to be shared with others
-
+* Mainstream OS (Operating System): Windows, Linux, Unix, OS-X
+* CLI: Command Line Interface, receives commands from user in the form of lines of text
+* GUI: Graphical User Interface, a system of interactive user components for computer software
+* Command: An instruction for the application to execute
+* Timeslot: An interval of time from HH:MM to HH:MM
+* Prefix: An abbreviation for the name of the parameter. Prefix should be entered before the actual parameter in a command and always ends with a slash (/).
+* MSS: Main success scenario
+
--------------------------------------------------------------------------------------------------------------------
## **Appendix: Instructions for manual testing**
@@ -338,40 +854,319 @@ testers are expected to do more *exploratory* testing.
1. Initial launch
- 1. Download the jar file and copy into an empty folder
+ 1. Download the jar file and copy into an empty folder
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
1. Saving window preferences
- 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
+ 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
- 1. Re-launch the app by double-clicking the jar file.
+ 1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-1. _{ more test cases … }_
+### List tutee(s)
+
+1. Listing tutee(s)
+
+ 1. Prerequisites: At least one tutee in the tutee list.
+
+ 2. Test case: `list`
+ Expected: Shows the full list of tutees in the tutee list.
+
+ 3. Test case: `list mon`
+ Expected: Shows tutees whose lessons fall on Monday in the tutee list.
+
+ 4. Test case: `list unpaid`
+ Expected: Shows tutees who have yet to pay for their lessons in the tutee list.
+
+### Adding a tutee
+
+1. Adding a tutee into the list.
+ 1. Prerequisites: None
+ 2. Test case: `add n/Betsy Crowe p/92939402 e/betsycrowe@example.com a/Newgate Prison sb/Secondary 3 Physics d/mon b/1900 end/1930 pr/35.00`
+ Expected: The tutee is added into the bottom of the list. Details of the added tutee is shown in the status message.
+
+
+2. Adding a duplicate tutee into the list
+ 1. Prerequisites: Completing the first test case for [Adding a tutee](#adding-a-tutee)
+ 2. Test case: `add n/Betsy Crowe p/92939402 e/betsycrowe@example.com a/Newgate Prison sb/Secondary 3 Physics d/mon b/1900 end/1930 pr/35.00`
+ Expected: The error message _This tutee already exists_ should be displayed in the status message.
+
+
+3. Adding a tutee that has clashing schedules.
+ 1. Prerequisites: Completing the first test case for [Adding a tutee](#adding-a-tutee)
+ 2. Test case: `add n/Jason Antonius p/12345678 e/test@gmail.com a/PGPR Residences sb/CS1101S d/mon b/1900 end/1930 pr/20`
+ Expected: The error message _This date and time clashes with an existing schedule_ should be displayed in the status message.
+
+### Deleting a tutee
+
+1. Deleting a tutee while all tutees are being shown
+ 1. Prerequisites: List all tutees using the `list` command. Multiple tutees in the list.
+ 2. Test case: `delete 1`
+ Expected: First tutee is deleted from the list. Details of the deleted tutee shown in the status message. Timestamp in the status bar is updated.
+
+
+### Finding a tutee
+
+1. Finding a tutee by their name
+ 1. Prerequisites: Have the default tutee data list.
+ 2. Test case: `find n/Betsy`
+ Expected: Tutees that contain the name predicate listed.
+
+
+2. Finding a tutee by their subject
+ 1. Prerequisites: Have the default tutee data list.
+ 2. Test case: `find sb/Maths`
+ Expected: Tutees that contain the subject predicate listed.
+
+
+3. Finding a tutee by their name and subject
+ 1. Prerequisites: Have the default tutee data list.
+ 2. Test case: `find n/Betsy sb/Maths`
+ Expected: Tutees that contain both the name predicate and subject predicate listed.
+
+
+4. Finding a tutee by their multiple word names
+ 1. Prerequisites: Have the default tutee data list.
+ 2. Test case: `find n/Betsy Crower`
+ Expected: The error message _Name can only take one word._
+
+
+5. Finding a tutee by their multiple word subject
+ 1. Prerequisites: Have the default tutee data list.
+ 2. Test case: `find sb/English Language`
+ Expected: The error message _Subject can only take one word._
+
+
+### Editing a tutee
+
+1. Editing a tutee while all tutees are being shown
+ 1. Prerequisites: Have the default tutee data and list all tutees using the `list` command.
+
+ 2. Test case: `edit 2 n/Betsy Crower a/Betsy street, block 110, #03-02`
+ Expected: Tutee is successfully edited, and the details of the edited tutee is shown in the status message.
+
+
+
+2. Editing a tutee that causes duplicate tutees
+
+ 1. Prerequisites: Have the default tutee data and list all tutees using the `list` command.
+
+ 2. Test case: `edit 2 n/Alex Yeoh p/87438807`
+ Expected: The error message _This tutee already exists_ should be displayed in the status message.
+
+
+
+3. Editing a tutee that causes clashing schedules.
+
+ 1. Prerequisites: Have the default tutee data and list all tutees using the `list` command.
+
+ 2. Test case: `edit 2 d/Mon b/2000 end/2200`
+ Expected: The error message _This date clashes with an existing schedule_ should be displayed in the status message.
+
+### Find free time
+
+1. Finding free time that results in no available timeslots
+ 1. Prerequisites: Have the default tutee data.
+
+ 2. Test case: `freeTime d/Mon dur/30 b/2000 end/2100`
+ Expected: The result
+ _Here is your list of free time:_
+ _There are no available timeslots._
+ should be displayed in the status message.
+
+
+
+2. Finding free time that results in available timeslots
+ 1. Prerequisites: Have the default tutee data.
+
+ 2. Test case: `freeTime d/Mon dur/30 b/1930 end/2130`
+ Expected: The result
+
+ _Here is your list of free time:_
+ _Free from 19:30 - 20:00_
+ _Free from 21:00 - 21:30_
+ should be displayed in the status message.
+
+
+### Marking a tutee as paid
+
+1. Marking a tutee as paid while all tutees are being shown
+
+ 1. Prerequisites: List all tutees using the `list` command. Multiple tutees in the list.
+
+ 1. Test case: `paid 2`
+ Expected: Second tutee is from the list is marked as paid. The message of marking person paid success will be shown. Timestamp in the status bar is updated.
+
+ 1. Test case: `delete 0`
+ Expected: No tutee is marked as paid. Error details shown in the status message. Status bar remains the same.
+
+ 1. Other incorrect paid commands to try: `paid`, `paid x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous.
+
+### Marking a tutee as not paid
+
+1. Marking a tutee as not paid while all tutees are being shown
+
+ 1. Prerequisites: List all tutees using the `list` command. Multiple tutees in the list.
+
+ 2. Test case: `unpaid 3`
+ Expected: Second tutee is from the list is marked as not paid. The message of marking person not paid success will be shown. Timestamp in the status bar is updated.
+
+ 3. Test case: `unpaid 0`
+ Expected: No tutee is marked as not paid. Error details shown in the status message. Status bar remains the same.
+
+ 4. Other incorrect unpaid commands to try: `unpaid`, `unpaid x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous.
+
+### Listing all unpaid tutees
+
+1. All tutees who haven't paid will be shown
+ 1. There are tutees in the list.
+
+ 2. Test case: `list unpaid`
+ Expected: All tutees who haven't paid will be shown. The message of how many tutees are unpaid will be shown. Timestamp in the status bar is updated.
+
+### Undo command
+
+1. Undo previous commands that can modify the data of tutees.
+ 1. Prerequisites: At least one tutee is present in the tutee list. Execute any command that modify tutee data. In this instruction, `clear` is used.
+ 2. Test case: `undo`
+ Expected: Restore all tutees that were cleared. A message informing the user that the command is successfully undone is displayed.
+
+
+2. Undo when there are no previous commands that modify the data of tutees.
+ 1. Prerequisites: Launch the application. Ensure no commands that modify the tutee data is executed.
+ 2. Test case: `undo`
+ Expected: No command is undone. Error details shown in the status message.
+
+### Redo command
+
+1. Redo a command when there is a undo command executed previously.
+ 1. At least one tutee is present in the tutee list. Execute any command that modify tutee data. In this instruction, `clear` is used followed by `undo`.
+ 2. Test case: `redo`
+ Expected: Clear the tutee list again.
+
+
+2. Redo a command when there is no undo command executed previously to redo.
+ 1. Prerequisites: Ensure no `undo` command is executed after launching the application.
+ 2. Test case: `redo`
+ Expected: No command is redone. Error details shown in the status message.
+
+### Manually editing data file
+
+
+**:information_source: Info:**
+This section assumes that you are an advanced user and understand some basic computing terminologies
+
+
+The default save file is called `"tuitionconnect.json"`.
+
+Below is an example of a valid save file format:
+```
+{
+ "persons" : [ {
+ "name" : "Bernice Yu",
+ "phone" : "99272758",
+ "email" : "berniceyu@example.com",
+ "address" : "Blk 30 Lorong 3 Serangoon Gardens, #07-18",
+ "subject" : "Physics",
+ "day" : "TUESDAY",
+ "begin" : "1000",
+ "end" : "1100",
+ "paid" : false,
+ "payRate" : "25.00"
+ }, {
+ "name" : "Charlotte Oliveiro",
+ "phone" : "93210283",
+ "email" : "charlotte@example.com",
+ "address" : "Blk 11 Ang Mo Kio Street 74, #11-04",
+ "subject" : "Chemistry",
+ "day" : "WEDNESDAY",
+ "begin" : "1200",
+ "end" : "1300",
+ "paid" : false,
+ "payRate" : "30.00"
+ } ]
+}
+```
+
+| Parameter | Description | Requirement / Remarks |
+|---------------|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|
+| **`name`** | Name of tutee | [Alphanumeric](#glossary) and may contain spaces |
+| **`date`** | Date of the upcoming application task | In **dd-mm-yyyy** format |
+| **`phone`** | Contact number of tutee | Any number at least 3 digits long |
+| **`email`** | Email address of tutee | In **XXXXXXXX@emaildomain** format Example: `johndoee@gmail.com` |
+| **`address`** | Address of the tutee | [Alphanumeric](#glossary) and may contain spaces |
+| **`subject`** | Subject of the tutee | [Alphanumeric](#glossary) and may contain spaces |
+| **`day`** | Day of weekly recurring lesson of the tutee | Full name of day or first three letters of the full name **Non-case sensitive** Example: `Mon`/`Monday`/`monday` |
+| **`begin`** | Begin time of a tutee's weekly recurring lesson | In **HHMM** format |
+| **`end`** | End time of a tutee's weekly recurring lesson | In **HHMM** format |
+| **`paid`** | Indicates if the tutee paid | boolean value for whether tutee has paid |
+| **`payrate`** | dollars per hour you make teaching this tutee | Numbers up to two decimal places only Numbers must be **non-negative** |
+
+
+## **Planned Enhancements**
+
+### Batch Processing for Paid Command
+
+Reason: This enhancement allows users to mark multiple persons as paid in a single command, improving efficiency.
+
+Idea: Modify the paid command parser to accept a list of person identifiers (e.g., "paid 1, 2, 3"), and update the command execution logic to iterate through the list and mark each person as paid.
+
+### Scheduled Unpaid Marking
+
+Reason: Introduce a scheduling feature within the unpaid command to set future unpaid statuses for individuals. This would be beneficial for scenarios where payments should automatically lapse after a set period.
+
+Idea: Add a scheduling mechanism within the command execution to mark individuals as unpaid after a specified future date or duration.
+
+### Find using multiple keywords
+
+Reason: To create a more sophisticated find feature for the best results. This enhancement allows users to get more specific results tailored to their criteria.
+
+Idea: Modify the `NameContainsKeywordsPredicate` and `SubjectContainsKeywordsPredicate` to accept multiple word inputs (e.g. "find n/Alex Yeoh sb/Maths Chemistry).
+
+### Maximum PayRate
+
+Reason: `PayRate` that are extremely high may not be displayed properly by GUI and are unlikely to be realistic PayRates per hour anyway.
+
+Idea: Modify the VALIDATION_REGEX of `PayRate` such that it only accepts values up to 9999.99.
+
+### Prevent Commands meant to Modify Tutee Data from Not Changing the Data
-### Deleting a person
+Reason: `clear`,`edit`, `paid`, `delete`,`unpaidAll` are commands that deal with modifying tutee data. If the tutee's `isPaid` status is true,
+the system permits the user to execute the `paid` command even though this will not change the `isPaid` status of the tutee. If these commands
+do not change the tutee data in any way but still allowed to be executed, when the user executes `undo`, there will be no changes since there are now duplicate states of the `VersionedAddressBook`
+is saved. The system should inform the user that this command will not modify any data and prevent the command from executing.
-1. Deleting a person while all persons are being shown
+Idea: Create a `Model#isSameData()` to compare whether the state of the tutee data before and after the command execution will be the same. If
+`Model#isSameData()` returns true, a `CommandException` should be thrown and the system should inform the user that this command will not modify any data.
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+### Enable Group Lessons
- 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.
+Reason: Current `Lesson` implementation prevents any lessons clashes, that is no two `Person` objects can have lessons that fall in same timeslots.
+However, this is based on the assumption that lessons are carried out on a one-on-one basis. Given the possibility of group lessons, having such a feature
+would allow for more flexibility in the application.
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+Idea: Create a `GroupTag` field for `Lesson` class which contain an ID for each `Lesson` object if they are group lessons. When checking if two
+lesson clashes, allow for lesson clash if `Lesson` objects have the same IDs which means they are the same group lesson.
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous.
+### Enhance Edit Feature
+Reason: Currently, the edit feature might result in a bug if the NAME and (DAY/BEGIN/END) fields are edited at the same time.
+For example, if tutee index 1 has the name John and has a lesson on Monday 20:00 - 21:00, trying to do `edit 1 n/Doe end/2030`
+will result in an error (throwing the message that this date clashes with an existing schedule). This is because it considers the
+pre-edited tutee as a schedule clash.
-1. _{ more test cases … }_
+Idea: Have an additional check with the index. If the edited person has the same index as the pre-edited person, then the
+system should allow the edit to happen.
-### Saving data
+### Put more restrictions in phone number and email
+Reason: Users might be able to put in an infinitely-long phone number and email which may cut off the UI
-1. Dealing with missing/corrupted data files
+Idea: Add the restriction in the parsers.
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+### UI Enhancements when minimizing the window size
+Reason: When the window is minimized, both the schedule list panel and the tutee list panel might be cut off.
-1. _{ more test cases … }_
+Idea: Set a minimum width for the Main Window to ensure the UI is not cut off.
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 57437026c7b..659242b6574 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,178 +3,501 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+# Welcome to TuitionConnect's User Guide!
+:rocket: Introducing **TuitionConnect**: Revolutionising your Private Tutoring Business! :rocket:
+
+Fed up with setting up numerous unorganised spreadsheets on Microsoft Excel or Google Sheets to handle the administrative side of your private tutoring business?
+
+Or feeling overwhelmed with the ugly-looking chaos of your Google Calendar as your number of teaching schedules increase?
+
+Say goodbye to all this mess with the help of **TuitionConnect**: the ultimate desktop app designed to streamline the administrative and financial tasks of your private tutoring business!
+
+This user guide will teach you how to install **TuitionConnect** from scratch, as well as providing information about the interesting features of **TuitionConnect**.
+
+
+
+## Table of Contents
+
+
+* [Welcome to TuitionConnect's User Guide!](#welcome-to-tuitionconnects-user-guide)
+ * [Table of Contents](#table-of-contents)
+ * [Introduction](#introduction)
+ * [Using this guide](#using-this-guide)
+ * [Symbols and Syntax](#symbols-and-syntax)
+ * [Layout](#layout)
+ * [Quick start](#quick-start)
+ * [Command Format](#command-format)
+ * [Parameters Requirement](#parameters-requirement)
+ * [Features](#features)
+ * [Viewing help : `help`](#viewing-help--help)
+ * [Adding a tutee : `add`](#adding-a-tutee--add)
+ * [Listing tutees : `list`](#listing-tutees--list)
+ * [Finding a tutee : `find`](#finding-a-tutee--find)
+ * [Editing a tutee : `edit`](#editing-a-tutee--edit)
+ * [Deleting a tutee: `delete`](#deleting-a-tutee-delete)
+ * [Clearing all entries : `clear`](#clearing-all-entries--clear)
+ * [Marking a tutee as paid : `paid`](#marking-a-tutee-as-paid--paid)
+ * [Marking a tutee as unpaid : `unpaid`](#marking-a-tutee-as-unpaid--unpaid)
+ * [Mark all tutee as unpaid: `unpaidAll`](#mark-all-tutee-as-unpaid-unpaidall)
+ * [Finding Free Time : `freeTime`](#finding-free-time--freetime)
+ * [Undo previous command : `undo`](#undo-previous-command--undo)
+ * [Redo previous undone command : `redo`](#redo-previous-undone-command--redo)
+ * [Calculating monthly revenue: `rev`](#calculating-monthly-revenue-rev)
+ * [Exiting the program : `exit`](#exiting-the-program--exit)
+ * [FAQ](#faq)
+ * [Known issues](#known-issues)
+ * [Command summary](#command-summary)
+ * [Glossary](#glossary)
+ * [Alphanumeric](#alphanumeric)
+ * [CLI](#cli)
+ * [Command](#command)
+ * [GUI](#gui)
+ * [Index](#index)
+ * [JAR file](#jar-file)
+ * [Java](#java)
+ * [JSON file](#json-file)
+ * [Parameter](#parameter)
+
-* Table of Contents
-{:toc}
--------------------------------------------------------------------------------------------------------------------
+## Introduction
+TuitionConnect is a **desktop app** built for private tutors and private tutoring businesses to simplify the process of
+administration and finance management, optimized for use via a **Command Line Interface** (CLI) while
+still having the benefits of a Graphical User Interface (GUI).
+
+If you love to type, then **TuitionConnect** is the app for you! It helps you to track tutee-specific details, teaching-schedule management,
+and other financial and administrative tasks faster than your old and conventional apps like Google Calendar or Microsoft Excel.
+
+Even if you are not a huge fan of typing, panic not! **TuitionConnect** was designed with you in mind! With this comprehensive User Guide,
+paired up with simple and beginner-friendly features, anyone can learn how to use **TuitionConnect** in no time!
+
+:sparkles: **Key Features:**
+1. **Effortless Administration:** Manage your tutoring schedules, student details, and lesson plans in one central hub. No more endless scrolling through messy spreadsheets or confusing calendar views!
+2. **Finance Made Easy**: Keep track of payments, and monitor your earnings effortlessly.
+3. **Calendar Sanity**: Prevent clashes in your schedules, and find slots where you are available!
+
+:bulb: **Make the Switch Today!**
+Transform your tutoring experience with **TuitionConnect!** Jump straight to the [Quick Start Section](#quick-start) and experience **TuitionConnect** now!
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+
+
+## Using this guide
+If you're feeling a bit lost, worry not!
+This user guide is to assist you seamlessly incorporate this application into your private tutoring business operations.
+
+For first-time users, we understand how it feels to open up an application without proper instructions. Thus, we have
+carefully crafted a [Quick Start](#quick-start) section in this guide to provide you with the knowledge it takes to
+start using TuitionConnect for your business.
+
+The [Layout](#layout) will also help you understand the different components of TuitionConnect's GUI.
+
+Eager to learn more about what our application can do? Head over to the [Command Format](#command-format) section to
+learn more about the general formats of the commands and getting yourself prepared before
+delving into the [Features](#features) section where you are in for the ride of your life! The Features section contains
+the in-depth explanation for each command's format and use cases.
+
+At last, we have also included a [Command Summary](#command-summary) section for when you become proficient at using
+TuitionConnect to refer quickly to any commands that you may need!
+
+Still unsure about the more technical terms used in this guide? Fret not, refer to the [glossary](#glossary)
+to better understand all the technical jargons!
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+
+
+## Symbols and Syntax
+
+Throughout this User Guide, you might run into the following symbols and syntax.
+
+| Symbol/Syntax | Meaning |
+|-------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
+| :information_source: **Notes** | Information that you need to pay attention to. |
+| :bulb: **Tip** | Information that you may find helpful. |
+| :exclamation: **Caution** | Information that you need to know before executing a [command](#command). |
+| `Highlighted text block` | [Commands](#command) or [parameters](#parameter) that you can enter into our application, or text that is directly displayed in our application. |
+| [Hyperlinked text in blue](#symbols-and-syntax) | When it is pressed, it should lead you to another section in the document or to an external link. |
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+
+
+## Layout
+The image below describes TuitionConnect's layout with some description for each component.
+![Layout](images/Layout.png)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+
## Quick start
1. Ensure you have Java `11` or above installed in your Computer.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+2. Download the latest `TuitionConnect.jar` from [here](https://github.com/AY2324S1-CS2103T-F10-4/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 TuitionConnect.
-1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar TuitionConnect.jar` command to run the application.
+ A GUI similar to the below should appear in a few seconds. The left list contains information about your tutees. The right list displays your teaching schedule for the next 7 days. 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 the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
Some example commands you can try:
- * `list` : Lists all contacts.
-
- * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+ * `list` : Lists all tutees.
- * `delete 3` : Deletes the 3rd contact shown in the current list.
+ * `add n/John Doe p/98765432 e/johnny@example.com a/John street, block 123, #01-01 sb/Primary 4 Math d/wed b/1500 end/1600 pr/20.00` : Adds a tutee named `John Doe` to the list.
- * `clear` : Deletes all contacts.
+ * `delete 3` : Deletes the 3rd tutee shown in the current list.
- * `exit` : Exits the app.
+6. Refer to the [Features](#features) below for details of each command.
-1. Refer to the [Features](#features) below for details of each command.
-
---------------------------------------------------------------------------------------------------------------------
-
-## Features
+ [Back to top ↑](#welcome-to-tuitionconnects-user-guide)
-
+
-**:information_source: Notes about the command format:**
+## Command Format
* Words in `UPPER_CASE` are the parameters to be supplied by the user.
e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
-
-* Items with `…` after them can be used multiple times including zero times.
- e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
+ e.g `list [DAY]` can be used as `list` or as `list Mon`.
* 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 `n/NAME sb/SUBJECT`, `sb/SUBJECT n/NAME ` is also valid.
+
+* Extraneous parameters added after commands that do not take in parameters (such as `help`, `list`, `exit`, `undo`, `redo` and `clear`) will be ignored.
+ e.g. if the command typed is `undo 123`, it will be interpreted as `undo`.
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+
-* 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`.
+## Parameters Requirement
+Here are the [parameter](#glossary) requirements of commonly used parameters by [commands](#glossary) in the [**Features**](#features) section below.
-* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
-
+| Parameter | Description | Requirement / Remarks |
+|----------------|-------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **`NAME`** | Name of tutee | [Alphanumeric](#glossary) and may contain spaces |
+| **`DATE`** | Date of the upcoming application task | In **dd-mm-yyyy** format |
+| **`PHONE`** | Contact number of tutee | Any number at least 3 digits long |
+| **`EMAIL`** | Email address of tutee | In **XXXXXXXX@emaildomain** format Example: `johndoee@gmail.com` |
+| **`ADDRESS`** | Address of the tutee | [Alphanumeric](#glossary) and may contain spaces |
+| **`SUBJECT`** | Subject of the tutee | [Alphanumeric](#glossary) and may contain spaces |
+| **`DAY`** | Day of weekly recurring lesson of the tutee | Full name of day or first three letters of the full name **Non-case sensitive** Example: `Mon`/`Monday`/`monday` |
+| **`BEGIN`** | Begin time of a tutee's weekly recurring lesson | In **HHMM** format |
+| **`END`** | End time of a tutee's weekly recurring lesson | In **HHMM** format |
+| **`PAYRATE`** | dollars per hour you make teaching this tutee | Numbers up to two decimal places only Numbers must be **non-negative** |
+| **`INDEX`** | The index number of the tutee shown in the tutee list panel | Used in [`delete`](#deleting-a-tutee-delete) [`edit`](#editing-a-tutee--edit) [`unpaid`](#marking-a-tutee-as-unpaid--unpaid) and [`paid`](#marking-a-tutee-as-paid--paid) commands Must be a **positive number** Example: (1,2,3,...) |
+| **`DURATION`** | The duration of a time slot in **minutes** | Used in [`freeTime`](#finding-free-time--freetime) Must be a **positive integer** Example: (60,120,...) |
+
+
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+
+
+## Features
### Viewing help : `help`
-Shows a message explaning how to access the help page.
+**Description**: Redirects you to the user guide if you are ever lost.
+
+**Format**: `help`
+
+**Sample Execution**: `help`
+
+![Help after](images/HelpAfter.png)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+### Adding a tutee : `add`
+
+**Description**: Adds new tutees you are tutoring into the list.
+
+**Format**: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS sb/SUBJECT d/DAY b/BEGIN e/END pr/PAYRATE`
+
+:exclamation: Things that can cause the `add` command to fail:
+1. Inserting invalid inputs into the add command.
+ - :bulb: Check the [Parameter Requirements](#parameters-requirement) for valid parameter inputs.
+2. Adding a tutee that will result in duplicate tutees.
+ - :information_source: Two tutees are considered duplicates if they have the same name and phone number
+3. Adding a tutee that will result in clashing schedules.
+ - :bulb: Use the [`freeTime` command](#finding-free-time--freetime) to list down timings when you are available and prevent schedule clashses.
+
+**Examples**:
+* `add n/John Doe p/98765432 e/johnny@example.com a/John street, block 123, #01-01 sb/Primary 4 Math d/wed b/1500 end/1600 pr/20.00`
+* `add n/Betsy Crowe p/92939402 e/betsycrowe@example.com a/Newgate Prison sb/Secondary 3 Physics d/mon b/1900 end/1930 pr/35.00`
+
+**Sample Execution**: `add n/Betsy Crowe p/92939402 e/betsycrowe@example.com a/Newgate Prison sb/Secondary 3 Physics d/mon b/1900 end/1930 pr/35.00`
+
+![Add after](images/Add%20after.png)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+### Listing tutees : `list`
+
+**Description**: View the tutees that you are currently teaching.
+
+**Format**: `list [DAY]/[unpaid]`
+
+:information_source: The `DAY` and `unpaid` parameters are optional.
+ * Without stating a specified `DAY` or `unpaid`, `list` will display all of your tutees
+ * When `DAY` is specified, only tutees whose lessons matches the specified `DAY` will be displayed
+ * When `unpaid` is included, only tutees who have yet to pay for their lessons will be displayed
+ * `list` can only accept up to one parameter in a single command
-![help message](images/helpMessage.png)
-Format: `help`
+:exclamation: Things that can cause the `list` command to fail:
+1. If the `DAY` parameter does not adhere to the specified format, the system will treat this as an invalid command.
+ - :bulb: Check the [Parameter Requirements](#parameters-requirement) for valid parameter inputs.
+2. Adding both `DAY` and `unpaid` parameters.
-### Adding a person: `add`
+**Examples**:
+* `list`
+* `list monday`
+* `list unpaid`
-Adds a person to the address book.
+**Sample Execution**: `list monday`
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+![list by day command](images/ListByDayCommand.png)
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
-
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
-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`
+### Finding a tutee : `find`
-### Listing all persons : `list`
+**Description** : Find tutees quickly in your current list.
-Shows a list of all persons in the address book.
+**Format**: `find [n/NAME] [sb/SUBJECT]`
-Format: `list`
+:information_source: `find` requires at least one of the two fields to be able to find for tutees and is non-case sensitive.
-### Editing a person : `edit`
+:information_source: As long as the tutee's `NAME` and/or `SUBJECT` contains the given keywords, the tutee will be displayed.
+ * Example: `find n/AL` returns a tutee named "ALICE"
-Edits an existing person in the address book.
+:information_source: Both n/ and sb/ prefixes take one word as input each.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+:exclamation: Things that can cause `find` command to fail:
+1. Inserting invalid inputs into the find command.
+ - :bulb: Check the [Parameter Requirements](#parameters-requirement) for valid parameter inputs.
+2. Inserting multiple word inputs for any of the two fields.
+ - :exclamation: As mentioned above, both prefixes can only take one word each as input.
-* 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**:
+* `find n/Alex`
+* `find sb/Maths`
+* `find n/Alex sb/Maths`
-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.
+**Sample Execution**: `find n/Alex sb/Maths`
-### Locating persons by name: `find`
+![findAfter](images/findAfter.png)
-Finds persons whose names contain any of the given keywords.
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
-Format: `find KEYWORD [MORE_KEYWORDS]`
+### Editing a tutee : `edit`
-* 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`
+**Description**: Edit a tutee in your current list.
-Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
+**Format**: `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [s/SUBJECTS] [d/DAY] [b/BEGIN] [end/END] [pr/PAYRATE`]
-### Deleting a person : `delete`
+:information_source: `edit` requires at least one of the fields to be present to be able to edit.
-Deletes the specified person from the address book.
+:exclamation: Things that can cause `edit` command to fail:
+1. Inserting invalid inputs into the `edit` command.
+ - :bulb: Check the [Parameter Requirements](#parameters-requirement) for valid parameter inputs.
+2. Editing a tutee that will result in duplicate tutees.
+ - :information_source: Two tutees are considered duplicates if they have the same name and phone number.
+3. Editing a tutee that will result in clashing schedules.
+ - :bulb: Use the [`freeTime` command](#finding-free-time--freetime) to list down timings when you are available and prevent schedule clashes.
-Format: `delete INDEX`
-* Deletes the person at the specified `INDEX`.
-* The index refers to the index number shown in the displayed person list.
-* The index **must be a positive integer** 1, 2, 3, …
+**Examples**:
+* `edit 1 p/91234567 d/Sun`
+* `edit 2 n/Betsy Crower a/Betsy street, block 110, #03-02`
-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.
+**Sample Execution**: `edit 2 n/Betsy Crower a/Betsy street, block 110, #03-02`
+
+![editAfter](images/editAfter.png)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+### Deleting a tutee: `delete`
+
+**Description**: Deletes the specific tutee from your list.
+
+**Format**: `delete INDEX`
+
+:exclamation: Things that can cause `delete` command to fail:
+1. Inserting invalid `INDEX` into the `delete` command.
+ - :information_source: `INDEX` should not be smaller than 1 and larger than
+ the number of tutees in the list.
+
+**Examples**:
+* `list` followed by `delete 2` deletes the 2nd person in the list.
+
+**Sample Execution**: `delete 2`
+
+![deleteAfter](images/deleteAfter.png)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
### Clearing all entries : `clear`
-Clears all entries from the address book.
+**Description**: Clears all entries from your list to start from scratch.
-Format: `clear`
+**Format**: `clear`
-### Exiting the program : `exit`
+**Sample Execution**: `clear`
+
+![ClearCommand.png](images/ClearCommand.png)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+### Marking a tutee as paid : `paid`
+
+**Description**: Mark a specific tutee as paid in your list.
+
+**Format**: `paid INDEX`
+
+:exclamation: Things that can cause `paid` command to fail:
+1. Inserting invalid `INDEX` into the `paid` command.
+ - :information_source: `INDEX` should not be smaller than 1 and larger than
+ the number of tutees in the list.
-Exits the program.
+* **`INDEX`**: Numbers between 1 to the number of people inside the list.
-Format: `exit`
+**Examples**:
+* `list` followed by `paid 1` marks the first person as paid in the list.
-### Saving the data
+**Sample Execution**: `paid 1`
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+![paidexample](images/paidexample.png)
-### Editing the data file
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
-AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+### Marking a tutee as unpaid : `unpaid`
-
: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. Hence, it is recommended to take a backup of the file before editing it.
-
+**Description**: Mark a specific tutee as not paid in your list.
-### Archiving data files `[coming in v2.0]`
+**Format**: `unpaid INDEX`
-_Details coming soon ..._
+:exclamation: Things that can cause `unpaid` command to fail:
+1. Inserting invalid `INDEX` into the `unpaid` command.
+ - :information_source: `INDEX` should not be smaller than 1 and larger than
+ the number of tutees in the list.
+**Examples**:
+* `list` followed by `unpaid 2` marks the 2nd person as not paid in the list.
+
+**Sample Execution**: `unpaid 2`
+
+![unpaidexample](images/unpaidexample.png)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+### Mark all tutees in the list as unpaid: `unpaidAll`
+
+**Description** : Mark all tutees in your current displayed list as not paid.
+
+**Format**: `unpaidAll`
+
+**Sample Execution**: `unpaidAll`
+
+![unpaidAllexample](images/unpaidAllexample.png)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+### Finding Free Time : `freeTime`
+
+**Description**: Finds timeslots when you are free in your schedule.
+
+**Format**: `freeTime d/DAY dur/DURATION b/BEGIN end/END`
+
+:bulb: Tip on how to use this command: I want to know when I'm free on `DAY` between `BEGIN` and `END` for at least `DURATION` minutes long.
+
+**Example:**
+ - `freeTime d/Sat dur/120 b/0800 end/2200`
+ - :information_source: the command above will find timeslots when the user is free on `Sat` between `0800` and `2200`, where each timeslot is at least `120` minutes long.
+
+**Sample Execution**: `freeTime d/Sat dur/120 b/0800 end/2200`
+
+![freeTime after](images/freeTime%20after.png)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+### Undo previous command : `undo`
+
+**Description**: Undo the most recent command if you made a mistake.
+
+**Format**: `undo`
+
+:information_source: You can only undo `add`,`clear`,`delete`,`edit`,`redo`,`paid`,`unpaid` and `unpaidAll` commands.
+
+**Sample Execution**: `clear` followed by `undo`
+1. `clear` deletes all tutee in the tutee list
+
+![clearCommand](images/ClearCommand.png)
+
+2. `undo` restores all cleared tutees
+
+![undoCommand](images/undoCommand.png)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+### Redo previous undone command : `redo`
+
+**Description**: Redo the most recent command that was undone if you changed your mind.
+
+**Format**: `redo`
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+### Calculating monthly revenue: `rev`
+
+**Description**: Calculate the total monthly revenue from all your tutees.
+
+**Format**: `rev`
+
+:information_source: `rev` command is time-sensitive (i.e. If the lesson occurrences varies between months, the value adjusts accordingly)
+
+**Sample Execution:** `rev`
+
+![Revenue Command Success](images/RevenueCommandSuccess.png)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+### Exiting the program : `exit`
+
+**Description**: Exits the program when you are done.
+
+**Format**: `exit`
+
+:information_source: The application window closes automatically after you type the command `exit`
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+
--------------------------------------------------------------------------------------------------------------------
## FAQ
-**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+**Q**: How do I transfer my existing data to another machine?
+**A**: Overwrite the empty `tuitionconnect.json` file in the machine by deleting it and replacing it with the `tuitionconnect.json` that contains the data
+
+**Q**: Where is my data stored?
+**A**: Your data is stored locally in a file named `tuitionconnect.json`
+
+**Q**: Is there any way I can give feedback if I face any issues?
+**A**: You can submit your issues [here](https://github.com/AY2324S1-CS2103T-F10-4/tp/issues)
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
--------------------------------------------------------------------------------------------------------------------
@@ -182,16 +505,80 @@ _Details coming soon ..._
1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again.
+
+
+
--------------------------------------------------------------------------------------------------------------------
## Command summary
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX` e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+| Action | Format, Examples |
+|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **help** | `help` |
+| **add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS s/SUBJECT d/DAY b/BEGIN end/END pr/PAYRATE` e.g., `add n/John Doe p/98765432 e/johnny@example.com a/John street, block 123, #01-01 sb/Primary 4 Math d/wed b/1500 end/1600 pr/20.00` |
+| **delete** | `delete INDEX` e.g., `delete 3` |
+| **clear** | `clear` |
+| **edit** | `edit INDEX n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS s/SUBJECTS d/DAY b/BEGIN end/END pr/PAYRATE` e.g.,`edit p/91234567 d/Sun` |
+| **list** | `list` |
+| **find** | `find n/[NAME] sb/[SUBJECT]` e.g., `find n/Alex sb/Math`, `find n/Alex`, `find sb/Maths` |
+| **list by day** | `list DAY` e.g., `list Monday` |
+| **paid** | `paid INDEX` e.g., `paid 1` |
+| **unpaid** | `unpaid INDEX` e.g., `unpaid 1` |
+| **list unpaid** | `list unpaid` |
+| **unpaidAll** | `unpaidAll` |
+| **freeTime** | `d/DAY dur/DURATION b/BEGIN end/END` |
+| **undo** | `undo` |
+| **redo** | `redo` |
+| **rev** | `rev` |
+| **exit** | `exit` |
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
+
+## Glossary
+
+### Alphanumeric
+
+Characters that are either a number or a letter.
+
+### CLI
+
+`CLI` is short for `Command Line Interface`, which is a text-based user interface where a program connects to the user.
+Users interact with a system or application by the use of text commands, where the system will respond accordingly
+depending on the command specified.
+
+### Command
+
+The specific instruction for the application to carry out.
+
+### GUI
+
+`GUI` is short for `Graphical User Interface`, which is a user interface that incorporates interactive visual
+components _(such as icons, etc)_. Users interact with a system or application by clicking on these components, where
+the system will respond accordingly to the user's actions by updating the user interface.
+
+### Index
+
+`Index` is the position of an item inside the list. If the `item` is at index 1, then we say that it is the first
+item on the list.
+
+### JAR file
+
+`JAR` is short for `Java ARchive`, which is a file format that is used to package the files to run TuitionConnect.
+Not only that, `JAR` files are also executables, meaning that it can be run by simply double-clicking the `JAR` file.
+
+### Java
+`Java` is the programming language used to develop TuitionConnect.
+A version of `Java` which is `Java 11` or higher is a requisite to run TuitionConnect on your devices.
+
+### JSON file
+
+`JSON` is short for JavaScript Object Notation, mainly utilized for storing data of users in TuitionConnect.
+These type of files are lightweight and text-based, making it suitable for both humans to read and write,
+as well as machines to parse and generate.
+
+### Parameter
+
+`Parameter`s are the inputs that a user can provide for a particular part of a command. As an example, for the field
+`n/NAME` of add command, `NAME` can be substituted with the input that you want to specify (such as n/John or n/Alice).
+
+[Back to top ↑](#welcome-to-tuitionconnects-user-guide)
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..35b99db9214 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "TuitionConnect"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2324S1-CS2103T-F10-4/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..b096993d4ec 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: "TuitionConnect";
font-size: 32px;
}
}
diff --git a/docs/diagrams/AddActivityDiagram.puml b/docs/diagrams/AddActivityDiagram.puml
new file mode 100644
index 00000000000..8dc3abe7119
--- /dev/null
+++ b/docs/diagrams/AddActivityDiagram.puml
@@ -0,0 +1,17 @@
+@startuml
+skinparam conditionStyle diamond
+
+start
+:User executes Add command;
+note :Valid Input Format: \n"add n/NAME p/PHONE e/EMAIL \na/ADDRESS sb/SUBJECT d/DAY \nb/START end/END pr/PAYRATE"
+:Checks for duplicate tutees and clashing schedules;
+if () then ([Adding this tutee will not \nresult in duplicate tutees or \nclashing schedules]\n)
+ :Add tutee to tutee list;
+
+else ([Else]\n)
+ :Display error message;
+endif
+
+stop
+
+@enduml
diff --git a/docs/diagrams/AddSequenceDiagram.puml b/docs/diagrams/AddSequenceDiagram.puml
new file mode 100644
index 00000000000..13b5ff2218a
--- /dev/null
+++ b/docs/diagrams/AddSequenceDiagram.puml
@@ -0,0 +1,90 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR
+participant "a:AddCommand" as AddCommand 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 n/John Doe p/98765432 \ne/johnd@example.com a/311, Clementi Ave 2, #02-25 \nsb/Maths d/Mon b/1200 end/1300 pr/20.00")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("add n/John Doe p/98765432 \ne/johnd@example.com a/311, Clementi Ave 2, #02-25 \nsb/Maths d/Mon b/1200 end/1300 pr/20.00")
+activate AddressBookParser
+
+create AddCommandParser
+AddressBookParser -> AddCommandParser
+activate AddCommandParser
+
+AddCommandParser --> AddressBookParser
+deactivate AddCommandParser
+
+AddressBookParser -> AddCommandParser : parse("n/John Doe p/98765432 \ne/johnd@example.com a/311, Clementi Ave 2, #02-25 \nsb/Maths d/Mon b/1200 end/1300 pr/20.00")
+activate AddCommandParser
+
+create AddCommand
+AddCommandParser -> AddCommand
+activate AddCommand
+
+AddCommand --> AddCommandParser : a
+deactivate AddCommand
+
+AddCommandParser --> AddressBookParser : a
+deactivate AddCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+AddCommandParser -[hidden]-> AddressBookParser
+destroy AddCommandParser
+
+AddressBookParser --> LogicManager : a
+deactivate AddressBookParser
+
+LogicManager -> AddCommand : execute()
+activate AddCommand
+
+AddCommand -> Model : purgeAddressBook()
+activate Model
+
+Model --> AddCommand
+deactivate Model
+
+AddCommand -> Model : addPerson(toAdd)
+activate Model
+
+note right
+toAdd:
+Person object containing
+all the information parsed
+from command input
+end note
+
+Model --> AddCommand
+deactivate Model
+
+AddCommand -> Model : commitAddressBook()
+activate Model
+
+Model --> AddCommand
+deactivate Model
+
+
+create CommandResult
+AddCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddCommand
+deactivate CommandResult
+
+AddCommand --> LogicManager : result
+deactivate AddCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
index 40ea6c9dc4c..40541cf8a19 100644
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ b/docs/diagrams/DeleteSequenceDiagram.puml
@@ -49,12 +49,25 @@ deactivate AddressBookParser
LogicManager -> DeleteCommand : execute()
activate DeleteCommand
+DeleteCommand -> Model : purgeAddressBook()
+activate Model
+
+Model --> DeleteCommand
+deactivate Model
+
DeleteCommand -> Model : deletePerson(1)
activate Model
Model --> DeleteCommand
deactivate Model
+DeleteCommand -> Model : commitAddressBook()
+activate Model
+
+Model --> DeleteCommand
+deactivate Model
+
+
create CommandResult
DeleteCommand -> CommandResult
activate CommandResult
diff --git a/docs/diagrams/EditActivityDiagram.puml b/docs/diagrams/EditActivityDiagram.puml
new file mode 100644
index 00000000000..7336a7238da
--- /dev/null
+++ b/docs/diagrams/EditActivityDiagram.puml
@@ -0,0 +1,17 @@
+@startuml
+skinparam conditionStyle diamond
+
+start
+:User executes Edit command;
+note :Example of Valid Input Format: \n"edit n/NAME p/PHONE e/EMAIL \na/ADDRESS sb/SUBJECT d/DAY \nb/START end/END pr/PAYRATE"
+:Checks for duplicate tutees and clashing schedules;
+if () then ([Tutee index\nnot in list\nor has clashing \nschedules with \nother tutees\nor edited fields\nwill result in\nduplicate tutees]\n)
+ :Display Error Message;
+
+else ([Else]\n)
+ :Edits the tutee;
+endif
+
+stop
+
+@enduml
diff --git a/docs/diagrams/EditSequenceDiagram.puml b/docs/diagrams/EditSequenceDiagram.puml
new file mode 100644
index 00000000000..102907e6c72
--- /dev/null
+++ b/docs/diagrams/EditSequenceDiagram.puml
@@ -0,0 +1,141 @@
+@startuml
+!include style.puml
+skinparam noteTextAlignment left
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant "parser:EditCommandParser" as EditCommandParser LOGIC_COLOR
+participant "descriptor:EditPersonDescriptor" as EditPersonDescriptor LOGIC_COLOR
+participant "command:EditCommand" as EditCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "model:Model" as Model MODEL_COLOR
+
+end box
+[-> LogicManager : execute("edit 1 n/Alex sb/Maths")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("edit 1 n/Alex sb/Maths")
+activate AddressBookParser
+
+create EditCommandParser
+AddressBookParser -> EditCommandParser
+activate EditCommandParser
+
+EditCommandParser --> AddressBookParser : parser
+deactivate EditCommandParser
+
+AddressBookParser -> EditCommandParser : parse(" 1 n/Alex sb/Maths")
+activate EditCommandParser
+
+'note right
+'//(omitted to reduce diagram complexity)//
+'**parse** method argument is processed as follows:
+'* separated into prefixes ("n/","p/", etc) using the ArgumentTokenizer class,
+'* have accompanying values (John Doe, Banana, 5, etc)
+'encapsulated in relevant data classes (Name, Phone, etc)
+'* passed as arguments for Order instantiation
+'end note
+
+create EditPersonDescriptor
+EditCommandParser -> EditPersonDescriptor
+activate EditPersonDescriptor
+
+EditPersonDescriptor --> EditCommandParser : descriptor
+deactivate EditPersonDescriptor
+
+create EditCommand
+EditCommandParser -> EditCommand : EditCommand(index, editPersonDescriptor);
+activate EditCommand
+
+note left
+index:
+the index of person to be edited
+
+editPersonDescriptor:
+a person object where its field are the edited values
+end note
+
+EditCommand --> EditCommandParser : command
+deactivate EditCommand
+
+EditCommandParser --> AddressBookParser : command
+deactivate EditCommandParser
+
+AddressBookParser --> LogicManager : command
+deactivate AddressBookParser
+
+destroy EditCommandParser
+
+LogicManager -> EditCommand : execute(model)
+activate EditCommand
+
+EditCommand -> Model : getSortedPersonList()
+activate Model
+
+Model --> EditCommand : lastShownList
+deactivate Model
+
+EditCommand -> EditCommand: createEditedPerson(personToEdit, editPersonDescriptor)
+activate EditCommand
+
+note right
+personToEdit:
+the person to be edited
+
+editPersonDescriptor
+a person object where its field are the edited values
+end note
+
+EditCommand --> EditCommand: editedPerson
+deactivate EditCommand
+
+EditCommand -> Model : purgeAddressBook()
+activate Model
+deactivate Model
+
+EditCommand -> Model : setPerson(personToEdit, editedPerson, isEditingSchedule)
+activate Model
+deactivate Model
+
+note right
+personToEdit:
+the person to be edited
+
+editedPerson:
+the editedPerson
+
+isEditingSchedule:
+boolean to check if day,begin, and end fields are being edited
+end note
+
+EditCommand -> Model : updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS)
+activate Model
+deactivate Model
+
+note right
+PREDICATE_SHOW_ALL_PERSONS:
+Predicate to show all persons
+end note
+
+EditCommand -> Model : commitAddressBook();
+activate Model
+deactivate Model
+
+create CommandResult
+EditCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> EditCommand
+deactivate CommandResult
+
+EditCommand --> LogicManager : result
+deactivate EditCommand
+EditCommand -[hidden]-> LogicManager : result
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/FindSequenceDiagram.puml b/docs/diagrams/FindSequenceDiagram.puml
new file mode 100644
index 00000000000..5d403915add
--- /dev/null
+++ b/docs/diagrams/FindSequenceDiagram.puml
@@ -0,0 +1,104 @@
+@startuml
+!include style.puml
+skinparam noteTextAlignment left
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant "parser:FindCommandParser" as FindCommandParser LOGIC_COLOR
+participant "predicate:NameContainsKeywordPredicate" as NameContainsKeywordPredicate LOGIC_COLOR
+participant "subject:SubjectContainsKeywordPredicate" as SubjectContainsKeywordPredicate LOGIC_COLOR
+participant "nameSubject:NameSubjectPredicate" as NameSubjectPredicate LOGIC_COLOR
+participant "command:FindCommand" as FindCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "model:Model" as Model MODEL_COLOR
+
+end box
+[-> LogicManager : execute("find n/Alex sb/Maths")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("find n/Alex sb/Maths")
+activate AddressBookParser
+
+create FindCommandParser
+AddressBookParser -> FindCommandParser
+activate FindCommandParser
+
+FindCommandParser --> AddressBookParser : parser
+deactivate FindCommandParser
+
+AddressBookParser -> FindCommandParser : parse(" n/Alex sb/Maths")
+activate FindCommandParser
+
+create NameContainsKeywordPredicate
+FindCommandParser -> NameContainsKeywordPredicate
+activate NameContainsKeywordPredicate
+
+NameContainsKeywordPredicate --> FindCommandParser : predicate
+deactivate NameContainsKeywordPredicate
+
+create SubjectContainsKeywordPredicate
+FindCommandParser -> SubjectContainsKeywordPredicate
+activate SubjectContainsKeywordPredicate
+
+SubjectContainsKeywordPredicate --> FindCommandParser : subject
+deactivate SubjectContainsKeywordPredicate
+
+create FindCommand
+FindCommandParser --> FindCommand : FindCommand(predicate, subject)
+activate FindCommand
+
+note right
+predicate:
+the predicate for the prefix name n/
+
+subject:
+the predicate for the prefix subject sb/
+end note
+
+FindCommand --> FindCommandParser : command
+deactivate FindCommand
+
+FindCommandParser --> AddressBookParser : command
+deactivate FindCommandParser
+
+AddressBookParser --> LogicManager : command
+deactivate AddressBookParser
+
+destroy FindCommandParser
+
+LogicManager -> FindCommand : execute(model)
+activate FindCommand
+
+create NameSubjectPredicate
+FindCommand -> NameSubjectPredicate
+activate NameSubjectPredicate
+
+NameSubjectPredicate --> FindCommand : nameSubject
+deactivate NameSubjectPredicate
+
+
+FindCommand -> Model : updateFilteredPersonList(nameSubject)
+activate Model
+deactivate Model
+
+note left
+nameSubject:
+the predicate of both inputs for keywords n/ and sb/
+end note
+
+create CommandResult
+FindCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> FindCommand
+deactivate CommandResult
+
+FindCommand --> LogicManager: result
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/FreeTimeActivityDiagram.puml b/docs/diagrams/FreeTimeActivityDiagram.puml
new file mode 100644
index 00000000000..a9436436666
--- /dev/null
+++ b/docs/diagrams/FreeTimeActivityDiagram.puml
@@ -0,0 +1,19 @@
+@startuml
+skinparam conditionStyle diamond
+
+start
+:User executes freeTime command;
+note :Valid Input Format: \n"freeTime d/DAY dur/DURATION \nb/START end/END"
+:Finds the timeslots where the user is busy on DAY;
+:Static method from TimeSlot class returns a list of timeslots\nwhere the user is free based on the timeslots where the user is busy;
+:These list of timeslots are filtered so that each timeslot is at least\n DURATION long, and between START and END;
+if () then ([List of available timeslots is empty]\n)
+ :Displays the message:\nThere are no available timeslots;
+
+else ([Else]\n)
+ :Displays all the timeslots\ninside the list;
+endif
+
+stop
+
+@enduml
diff --git a/docs/diagrams/FreeTimeSequenceDiagram.puml b/docs/diagrams/FreeTimeSequenceDiagram.puml
new file mode 100644
index 00000000000..72ee54d273d
--- /dev/null
+++ b/docs/diagrams/FreeTimeSequenceDiagram.puml
@@ -0,0 +1,83 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":FreeTimeCommandParser" as FreeTimeCommandParser LOGIC_COLOR
+participant ":FreeTimeCommand" as FreeTimeCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":AddressBook" as AddressBook MODEL_COLOR
+participant ":UniquePersonList" as UniquePersonList MODEL_COLOR
+participant "<> \nTimeSlot" as TimeSlot MODEL_COLOR
+end box
+[-> LogicManager : execute("freeTime d/Sat dur/120 b/0700 end/1200")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("freeTime d/Sat dur/120 b/0700 end/1200")
+activate AddressBookParser
+
+create FreeTimeCommandParser
+AddressBookParser -> FreeTimeCommandParser
+activate FreeTimeCommandParser
+
+FreeTimeCommandParser --> AddressBookParser
+deactivate FreeTimeCommandParser
+
+AddressBookParser -> FreeTimeCommandParser : parse("d/Sat dur/120 b/0700 end/1200")
+activate FreeTimeCommandParser
+
+create FreeTimeCommand
+FreeTimeCommandParser -> FreeTimeCommand
+activate FreeTimeCommand
+FreeTimeCommand --> FreeTimeCommandParser
+deactivate FreeTimeCommand
+
+FreeTimeCommandParser --> AddressBookParser
+deactivate FreeTimeCommandParser
+
+FreeTimeCommandParser -[hidden]-> AddressBookParser
+destroy FreeTimeCommandParser
+
+AddressBookParser --> LogicManager
+deactivate AddressBookParser
+
+LogicManager -> FreeTimeCommand : execute()
+activate FreeTimeCommand
+
+FreeTimeCommand -> Model : findInterval(toFind)
+activate Model
+
+'ADD MORE HERE
+Model -> AddressBook : findInterval(interval)
+activate AddressBook
+AddressBook -> UniquePersonList : findInterval(interval)
+activate UniquePersonList
+UniquePersonList --> AddressBook
+deactivate UniquePersonList
+AddressBook --> Model
+deactivate AddressBook
+
+Model --> FreeTimeCommand
+deactivate Model
+
+FreeTimeCommand -> TimeSlot : parseIntervals(results)
+activate TimeSlot
+TimeSlot --> FreeTimeCommand : timeslots
+deactivate TimeSlot
+
+FreeTimeCommand -> TimeSlot : findAvailableTime(timeslots, toFind)
+activate TimeSlot
+TimeSlot --> FreeTimeCommand : availableTime
+deactivate TimeSlot
+
+FreeTimeCommand --> LogicManager: result
+deactivate FreeTimeCommand
+
+FreeTimeCommand -[hidden]-> LogicManager: result
+
+[<--LogicManager
+@enduml
diff --git a/docs/diagrams/ListByDaySequenceDiagram.puml b/docs/diagrams/ListByDaySequenceDiagram.puml
new file mode 100644
index 00000000000..d511925aa6b
--- /dev/null
+++ b/docs/diagrams/ListByDaySequenceDiagram.puml
@@ -0,0 +1,78 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":ListCommandParser" as ListCommandParser LOGIC_COLOR
+participant "listByDay:ListByDayCommand" as ListByDayCommand 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("list Mon")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("list Mon")
+activate AddressBookParser
+
+create ListCommandParser
+AddressBookParser -> ListCommandParser
+activate ListCommandParser
+
+ListCommandParser --> AddressBookParser
+deactivate ListCommandParser
+
+AddressBookParser -> ListCommandParser : parse("list Mon")
+activate ListCommandParser
+
+create ListByDayCommand
+ListCommandParser -> ListByDayCommand
+activate ListByDayCommand
+
+ListByDayCommand --> ListCommandParser : listByDay
+deactivate ListByDayCommand
+
+ListCommandParser --> AddressBookParser : listByDay
+deactivate ListCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+ListCommandParser -[hidden]-> AddressBookParser
+destroy ListCommandParser
+
+AddressBookParser --> LogicManager : listByDay
+deactivate AddressBookParser
+
+LogicManager -> ListByDayCommand : execute()
+activate ListByDayCommand
+
+ListByDayCommand -> Model : updateFilteredPersonList(DAY_PREDICATE)
+
+note right
+DAY_PREDICATE:
+Predicate to show tutees
+whose lessons are
+on the specified day
+end note
+
+activate Model
+
+Model --> ListByDayCommand
+deactivate Model
+
+create CommandResult
+ListByDayCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> ListByDayCommand
+deactivate CommandResult
+
+ListByDayCommand --> LogicManager : result
+deactivate ListByDayCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 0de5673070d..bd9c0ad12dc 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -13,12 +13,19 @@ Class ModelManager
Class UserPrefs
Class UniquePersonList
+Class Interval
Class Person
Class Address
Class Email
Class Name
Class Phone
-Class Tag
+Class Lesson
+Class PayRate
+Class Day
+Class Begin
+Class End
+Class Subject
+
Class I #FFFFFF
}
@@ -37,11 +44,18 @@ UserPrefs .up.|> ReadOnlyUserPrefs
AddressBook *--> "1" UniquePersonList
UniquePersonList --> "~* all" Person
+UniquePersonList ..> Interval
Person *--> Name
Person *--> Phone
Person *--> Email
Person *--> Address
-Person *--> "*" Tag
+Person *--> Subject
+Person *--> PayRate
+Person *--> Lesson
+Lesson ..> Day
+Lesson ..> Begin
+Lesson ..> End
+
Person -[hidden]up--> I
UniquePersonList -[hidden]right-> I
diff --git a/docs/diagrams/PaidSequenceDiagram.puml b/docs/diagrams/PaidSequenceDiagram.puml
new file mode 100644
index 00000000000..0abaf55a352
--- /dev/null
+++ b/docs/diagrams/PaidSequenceDiagram.puml
@@ -0,0 +1,83 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":PaidCommandParser" as PaidCommandParser LOGIC_COLOR
+participant "d:PaidCommand" as PaidCommand 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("paid 1")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("paid 1")
+activate AddressBookParser
+
+create PaidCommandParser
+AddressBookParser -> PaidCommandParser
+activate PaidCommandParser
+
+PaidCommandParser --> AddressBookParser
+deactivate PaidCommandParser
+
+AddressBookParser -> PaidCommandParser : parse("1")
+activate PaidCommandParser
+
+create PaidCommand
+PaidCommandParser -> PaidCommand
+activate PaidCommand
+
+PaidCommand --> PaidCommandParser : p
+deactivate PaidCommand
+
+PaidCommandParser --> AddressBookParser : p
+deactivate PaidCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+PaidCommandParser -[hidden]-> AddressBookParser
+destroy PaidCommandParser
+
+AddressBookParser --> LogicManager : p
+deactivate AddressBookParser
+
+LogicManager -> PaidCommand : execute()
+activate PaidCommand
+
+PaidCommand -> Model : purgeAddressBook()
+activate Model
+
+Model --> PaidCommand
+deactivate Model
+
+PaidCommand -> Model : markPersonPaid(1)
+activate Model
+
+Model --> PaidCommand
+deactivate Model
+
+PaidCommand -> Model : commitAddressBook()
+activate Model
+
+Model --> PaidCommand
+deactivate Model
+
+create CommandResult
+PaidCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> PaidCommand
+deactivate CommandResult
+
+PaidCommand --> LogicManager : result
+deactivate PaidCommand
+
+PaidCommand -[hidden]-> LogicManager
+destroy PaidCommand
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/RevenueSequenceDiagram.puml b/docs/diagrams/RevenueSequenceDiagram.puml
new file mode 100644
index 00000000000..45016a7bf20
--- /dev/null
+++ b/docs/diagrams/RevenueSequenceDiagram.puml
@@ -0,0 +1,70 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant "r:RevenueCommand" as RevenueCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+participant ":Person" as Person MODEL_COLOR
+participant ":PayRate" as PayRate MODEL_COLOR
+participant ":Lesson" as Lesson MODEL_COLOR
+end box
+
+[-> LogicManager : execute("rev")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("rev")
+activate AddressBookParser
+AddressBookParser -> RevenueCommand
+activate RevenueCommand
+
+RevenueCommand --> AddressBookParser: r
+deactivate RevenueCommand
+
+
+AddressBookParser --> LogicManager : r
+deactivate AddressBookParser
+
+LogicManager -> RevenueCommand : execute()
+activate RevenueCommand
+
+RevenueCommand -> Model : getUnfilteredPersonList()
+activate Model
+
+Model --> RevenueCommand
+deactivate Model
+
+loop number of tutees
+RevenueCommand -> Person : getMonthlyFee()
+activate Person
+Person -> PayRate : getValue()
+activate PayRate
+PayRate --> Person
+deactivate PayRate
+Person -> Lesson : getMonthlyHours()
+activate Lesson
+Lesson --> Person
+deactivate Lesson
+Person --> RevenueCommand : monthlyFee
+deactivate Person
+end
+
+create CommandResult
+RevenueCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> RevenueCommand
+deactivate CommandResult
+
+RevenueCommand --> LogicManager : result
+deactivate RevenueCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index a821e06458c..eadc48ec8ba 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -19,7 +19,6 @@ Class "<>\nAddressBookStorage" as AddressBookStorage
Class JsonAddressBookStorage
Class JsonSerializableAddressBook
Class JsonAdaptedPerson
-Class JsonAdaptedTag
}
}
@@ -38,6 +37,5 @@ JsonUserPrefsStorage .up.|> UserPrefsStorage
JsonAddressBookStorage .up.|> AddressBookStorage
JsonAddressBookStorage ..> JsonSerializableAddressBook
JsonSerializableAddressBook --> "*" JsonAdaptedPerson
-JsonAdaptedPerson --> "*" JsonAdaptedTag
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..5df40221068 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -15,6 +15,8 @@ Class PersonListPanel
Class PersonCard
Class StatusBarFooter
Class CommandBox
+Class ScheduleListPanel
+Class ScheduleCard
}
package Model <> {
@@ -35,8 +37,10 @@ MainWindow *-down-> "1" ResultDisplay
MainWindow *-down-> "1" PersonListPanel
MainWindow *-down-> "1" StatusBarFooter
MainWindow --> "0..1" HelpWindow
+MainWindow --> "1" ScheduleListPanel
-PersonListPanel -down-> "*" PersonCard
+PersonListPanel -down--> "*" PersonCard
+ScheduleListPanel -down-> "*" ScheduleCard
MainWindow -left-|> UiPart
@@ -47,7 +51,10 @@ PersonCard --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow --|> UiPart
+
+
PersonCard ..> Model
+ScheduleCard ..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml
index 87ff3e9237e..caa904f7ea8 100644
--- a/docs/diagrams/UndoSequenceDiagram.puml
+++ b/docs/diagrams/UndoSequenceDiagram.puml
@@ -38,6 +38,9 @@ Model -> VersionedAddressBook : undo()
activate VersionedAddressBook
VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook)
+activate VersionedAddressBook
+VersionedAddressBook --> VersionedAddressBook :
+deactivate VersionedAddressBook
VersionedAddressBook --> Model :
deactivate VersionedAddressBook
diff --git a/docs/diagrams/UnpaidAllSequenceDiagram.puml b/docs/diagrams/UnpaidAllSequenceDiagram.puml
new file mode 100644
index 00000000000..c67bd05d104
--- /dev/null
+++ b/docs/diagrams/UnpaidAllSequenceDiagram.puml
@@ -0,0 +1,91 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":UnPaidAllCommandParser" as UnPaidAllCommandParser LOGIC_COLOR
+participant "d:UnPaidAllCommand" as UnPaidAllCommand 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("unpaidAll")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("unpaidAll")
+activate AddressBookParser
+
+create UnPaidAllCommandParser
+AddressBookParser -> UnPaidAllCommandParser
+activate UnPaidAllCommandParser
+
+UnPaidAllCommandParser --> AddressBookParser
+deactivate UnPaidAllCommandParser
+
+AddressBookParser -> UnPaidAllCommandParser : parse("unpaidAll")
+activate UnPaidAllCommandParser
+
+create UnPaidAllCommand
+UnPaidAllCommandParser -> UnPaidAllCommand
+activate UnPaidAllCommand
+
+UnPaidAllCommand --> UnPaidAllCommandParser : p
+deactivate UnPaidAllCommand
+
+UnPaidAllCommandParser --> AddressBookParser : p
+deactivate UnPaidAllCommandParser
+
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+UnPaidAllCommandParser -[hidden]-> AddressBookParser
+destroy UnPaidAllCommandParser
+
+AddressBookParser --> LogicManager : p
+deactivate AddressBookParser
+
+LogicManager -> UnPaidAllCommand : execute()
+activate UnPaidAllCommand
+
+UnPaidAllCommand -> Model : purgeAddressBook()
+activate Model
+
+Model --> UnPaidAllCommand
+deactivate Model
+
+loop number of tutees
+ UnPaidAllCommand -> Model : markPersonUnPaid(index)
+ activate Model
+ Model --> UnPaidAllCommand
+ deactivate Model
+end loop
+
+UnPaidAllCommand -> Model : updateFilteredPersonList()
+activate Model
+
+Model --> UnPaidAllCommand
+deactivate Model
+
+UnPaidAllCommand -> Model : commitAddressBook()
+activate Model
+
+Model --> UnPaidAllCommand
+deactivate Model
+
+create CommandResult
+UnPaidAllCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> UnPaidAllCommand
+deactivate CommandResult
+
+UnPaidAllCommand -[hidden]-> LogicManager
+deactivate UnPaidAllCommand
+
+UnPaidAllCommand --> LogicManager : result
+destroy UnPaidAllCommand
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/images/Add after.png b/docs/images/Add after.png
new file mode 100644
index 00000000000..de176678a1b
Binary files /dev/null and b/docs/images/Add after.png differ
diff --git a/docs/images/Add before and after.png b/docs/images/Add before and after.png
new file mode 100644
index 00000000000..fdf49250ced
Binary files /dev/null and b/docs/images/Add before and after.png differ
diff --git a/docs/images/AddActivityDiagram.png b/docs/images/AddActivityDiagram.png
new file mode 100644
index 00000000000..a6af5ea2b41
Binary files /dev/null and b/docs/images/AddActivityDiagram.png differ
diff --git a/docs/images/AddSequenceDiagram.png b/docs/images/AddSequenceDiagram.png
new file mode 100644
index 00000000000..dfc9e42accc
Binary files /dev/null and b/docs/images/AddSequenceDiagram.png differ
diff --git a/docs/images/ClearCommand.png b/docs/images/ClearCommand.png
new file mode 100644
index 00000000000..f2650848a77
Binary files /dev/null and b/docs/images/ClearCommand.png differ
diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png
index e186f7ba096..55e6c86459b 100644
Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ
diff --git a/docs/images/EditActivityDiagram.png b/docs/images/EditActivityDiagram.png
new file mode 100644
index 00000000000..5bc0ad845ab
Binary files /dev/null and b/docs/images/EditActivityDiagram.png differ
diff --git a/docs/images/EditSequenceDiagram.png b/docs/images/EditSequenceDiagram.png
new file mode 100644
index 00000000000..4d0ed2b8379
Binary files /dev/null and b/docs/images/EditSequenceDiagram.png differ
diff --git a/docs/images/FindSequenceDiagram.png b/docs/images/FindSequenceDiagram.png
new file mode 100644
index 00000000000..198fdb1e328
Binary files /dev/null and b/docs/images/FindSequenceDiagram.png differ
diff --git a/docs/images/FreeTimeActivityDiagram.png b/docs/images/FreeTimeActivityDiagram.png
new file mode 100644
index 00000000000..6b232830d3b
Binary files /dev/null and b/docs/images/FreeTimeActivityDiagram.png differ
diff --git a/docs/images/FreeTimeSequenceDiagram.png b/docs/images/FreeTimeSequenceDiagram.png
new file mode 100644
index 00000000000..89dcdc8c8f6
Binary files /dev/null and b/docs/images/FreeTimeSequenceDiagram.png differ
diff --git a/docs/images/HelpAfter.png b/docs/images/HelpAfter.png
new file mode 100644
index 00000000000..cc751ce596c
Binary files /dev/null and b/docs/images/HelpAfter.png differ
diff --git a/docs/images/Layout.png b/docs/images/Layout.png
new file mode 100644
index 00000000000..f483df3baaf
Binary files /dev/null and b/docs/images/Layout.png differ
diff --git a/docs/images/ListByDayCommand.png b/docs/images/ListByDayCommand.png
new file mode 100644
index 00000000000..bfd13e30757
Binary files /dev/null and b/docs/images/ListByDayCommand.png differ
diff --git a/docs/images/ListByDaySequenceDiagram.png b/docs/images/ListByDaySequenceDiagram.png
new file mode 100644
index 00000000000..09db02f157d
Binary files /dev/null and b/docs/images/ListByDaySequenceDiagram.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index a19fb1b4ac8..5bc55e02fea 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/PaidSequenceDiagram.png b/docs/images/PaidSequenceDiagram.png
new file mode 100644
index 00000000000..bef961ac020
Binary files /dev/null and b/docs/images/PaidSequenceDiagram.png differ
diff --git a/docs/images/RevenueCommandSuccess.png b/docs/images/RevenueCommandSuccess.png
new file mode 100644
index 00000000000..6c0ed138178
Binary files /dev/null and b/docs/images/RevenueCommandSuccess.png differ
diff --git a/docs/images/RevenueSequenceDiagram.png b/docs/images/RevenueSequenceDiagram.png
new file mode 100644
index 00000000000..f446f4598b1
Binary files /dev/null and b/docs/images/RevenueSequenceDiagram.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 18fa4d0d51f..2ab0b2ab4ab 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 5bd77847aa2..6cd3bc210ac 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 11f06d68671..cf7d5fed275 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png
index c7a7e637266..d7e737548fb 100644
Binary files a/docs/images/UndoSequenceDiagram.png and b/docs/images/UndoSequenceDiagram.png differ
diff --git a/docs/images/UnpaidAllSequenceDiagram.png b/docs/images/UnpaidAllSequenceDiagram.png
new file mode 100644
index 00000000000..450fa7fb9ad
Binary files /dev/null and b/docs/images/UnpaidAllSequenceDiagram.png differ
diff --git a/docs/images/deleteAfter.png b/docs/images/deleteAfter.png
new file mode 100644
index 00000000000..766aa8cec75
Binary files /dev/null and b/docs/images/deleteAfter.png differ
diff --git a/docs/images/editAfter.png b/docs/images/editAfter.png
new file mode 100644
index 00000000000..28a13bdeecb
Binary files /dev/null and b/docs/images/editAfter.png differ
diff --git a/docs/images/findAfter.png b/docs/images/findAfter.png
new file mode 100644
index 00000000000..35ab3f35fbf
Binary files /dev/null and b/docs/images/findAfter.png differ
diff --git a/docs/images/freeTime after.png b/docs/images/freeTime after.png
new file mode 100644
index 00000000000..56f8cc3b4c2
Binary files /dev/null and b/docs/images/freeTime after.png differ
diff --git a/docs/images/freeTime before and after.png b/docs/images/freeTime before and after.png
new file mode 100644
index 00000000000..6486ea2263e
Binary files /dev/null and b/docs/images/freeTime before and after.png differ
diff --git a/docs/images/heran9.png b/docs/images/heran9.png
new file mode 100644
index 00000000000..1007a25b933
Binary files /dev/null and b/docs/images/heran9.png differ
diff --git a/docs/images/jovkusuma.png b/docs/images/jovkusuma.png
new file mode 100644
index 00000000000..ce8e021f00f
Binary files /dev/null and b/docs/images/jovkusuma.png differ
diff --git a/docs/images/lambraydon.png b/docs/images/lambraydon.png
new file mode 100644
index 00000000000..fa14d5e403d
Binary files /dev/null and b/docs/images/lambraydon.png differ
diff --git a/docs/images/listunpaidexample.png b/docs/images/listunpaidexample.png
new file mode 100644
index 00000000000..165de416d5d
Binary files /dev/null and b/docs/images/listunpaidexample.png differ
diff --git a/docs/images/paidexample.png b/docs/images/paidexample.png
new file mode 100644
index 00000000000..74c10a04336
Binary files /dev/null and b/docs/images/paidexample.png differ
diff --git a/docs/images/undoCommand.png b/docs/images/undoCommand.png
new file mode 100644
index 00000000000..f771909c37a
Binary files /dev/null and b/docs/images/undoCommand.png differ
diff --git a/docs/images/unpaidAllexample.png b/docs/images/unpaidAllexample.png
new file mode 100644
index 00000000000..ecdbc69f951
Binary files /dev/null and b/docs/images/unpaidAllexample.png differ
diff --git a/docs/images/unpaidexample.png b/docs/images/unpaidexample.png
new file mode 100644
index 00000000000..59e9941f8ed
Binary files /dev/null and b/docs/images/unpaidexample.png differ
diff --git a/docs/images/winstonleonard.png b/docs/images/winstonleonard.png
new file mode 100644
index 00000000000..efffe449fdc
Binary files /dev/null and b/docs/images/winstonleonard.png differ
diff --git a/docs/images/yihfei.png b/docs/images/yihfei.png
new file mode 100644
index 00000000000..b946612bc97
Binary files /dev/null and b/docs/images/yihfei.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..0b680bf19e5 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,6 +1,6 @@
---
layout: page
-title: AddressBook Level-3
+title: TuitionConnect
---
[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
@@ -8,10 +8,10 @@ title: AddressBook Level-3
![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).
+**TuitionConnect is a desktop application designed to streamline the administrative and financial tasks of your tuition business.** 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 TuitionConnect, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested about developing TuitionConnect, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
**Acknowledgements**
diff --git a/docs/team/heran9.md b/docs/team/heran9.md
new file mode 100644
index 00000000000..3abc476e6e7
--- /dev/null
+++ b/docs/team/heran9.md
@@ -0,0 +1,46 @@
+---
+layout: page
+title: Lang Heran's Project Portfolio Page
+---
+
+### Project: TuitionConnect
+
+TuitionConnect is a **desktop app for simplifying the process of administration and finance management for private tutors, 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, you can maximise tracking tutee-specific details, teaching-schedule management, and finance management.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Add the ability to mark the student as paid/unpaid.
+ * What it does: Allow the user to mark their student has paid for their lessons or not paid for their lessons.
+ * Justification: This feature helps the users (private tutors) to track their students' payment statues.
+ * Highlights: The implementation of this feature set up the footstone for future financial management features. The implementation of this feature is not easy since it requires changes to other methods.
+ * Credits: The implementation of this feature learned from the work of DeleteCommand inside our implementation.
+
+* **New Feature**: Add the ability to reset all students in the current list as unpaid.
+ * What it does: Allow the user to reset all student shown in the list as unpaid for their lessons.
+ * Justification: This feature helps the users (private tutors) to reset all students' payment statues at the beginning of the month when there is new turn to get tuition fee.
+ * Highlights: This feature is a follow-up feature of paid/unpaid features, which allows the users to easily reset students payment statuses.
+
+* **New Feature**: Add the ability to list all unpaid students.
+ * What it does: Allow the user check all unpaid students in a list.
+ * Justification: This feature helps the users (private tutors) to easily see which students haven't paid.
+ * Highlights: This feature is a combination feature of list and paid features, combine the both functionalities of these two features,it comes up with a new and powerful feature.
+
+* **Code contributed**:
+ * [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=heran9&breakdown=true)
+
+* **Enhancements to existing features**:
+ * Enhancements to the list command. Add the ability to list all unpaid students.
+ * What it does: Allow the user check all unpaid students in a list.
+ * Justification: This feature helps the users (private tutors) to easily see which students haven't paid.
+ * Highlights: This feature is a combination feature of list and paid features, combine the both functionalities of these two features,it comes up with a new and powerful feature.
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `paid`, `unpaid`, `unpaidAll`, `list unpaid`
+ * Help to determine the overall format and structure of UG.
+ * Developer Guide:
+ * Added implementation details of the `paid`, `unpaid`, `list unpaid` features.
+ * Added Activity Diagrams for `paid` and `unpaidAll` features.
+
+* **Review/Mentoring contributions**:
+ * Reviewed and offered suggestions for team member's pull requests.
diff --git a/docs/team/jovkusuma.md b/docs/team/jovkusuma.md
new file mode 100644
index 00000000000..d1896fad89b
--- /dev/null
+++ b/docs/team/jovkusuma.md
@@ -0,0 +1,65 @@
+---
+layout: page
+title: Armando Jovan Kusuma's Project Portfolio Page
+---
+
+### Project: TuitionConnect
+TuitionConnect is a **desktop app** built for tutors and tutoring businesses to simplify the process of
+administration and finance management, optimized for use via a **Command Line Interface** (CLI) while
+still having the benefits of a Graphical User Interface (GUI).
+
+
+**Code contributed:** Click here for [RepoSense Link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=jovkusuma&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&tabAuthor=jovkusuma&tabRepo=AY2324S1-CS2103T-F10-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false).
+
+
+**Enhancements implemented:**
+
+* Refactored the `add` and `edit` command.
+ * Remove tags as parameter for both `add` and `edit` command.
+ * (Pull Requests [#90](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/90))
+
+* Enhance the `find` command.
+ * Allow users to find tutees using partial keywords.
+ * Allow users to find by name and/or subject to find for tutees.
+ * (Pull Requests [#68](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/68), [#127](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/127))
+
+* Enforce that `begin` time should strictly be before `end` time.
+ * Restrict users to only input `begin` times which are before `end` times.
+ * (Pull Requests [#111](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/111))
+
+* Write and fixing test cases:
+ * Fixed the test cases for `StringUtilTest`, `AddressBookParserTest`, `FindCommandTest`, `FindCommandParserTest`, `NameContainsKeywordsPredicateTest`
+ to support refactored `find` command (Pull Requests [#68](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/68), [#127](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/127)).
+ * Fixed the test cases for `AddCommandTest`, `EditCommandTest` to support the enforcement of begin time less than end time (Pull Requests [#111](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/111)).
+ * Wrote test cases for `SubjectContainsKeywordsPredicateTest`, `NameSubjectPredicateTest`
+ to test the `find` Command (Pull Requests [#127](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/127), [#199](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/199)).
+
+**Contributions to the UG:**
+
+* Added `Using this guide` and `Glossary` sections (Pull Requests [#209](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/209)).
+* Wrote features and descriptions for `help`, `find`, `edit` and `delete` (Pull Requests [#235](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/235)).
+
+
+**Contributions to the DG:**
+
+* Added use cases for `add`, `edit` and `find` commands (Pull Requests [#102](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/102), [#210](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/210)).
+* Added description and sequence diagram for `find` and `edit` commands (Pull Requests [#197](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/197)).
+* Wrote manual test cases for `find` command (Pull requests [#234](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/234)).
+* Wrote planned enhancements (Pull Requests [#210](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/210)).
+
+
+**Contributions to team-based tasks**:
+
+
+* Updating config.yml (Pull Requests [#53](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/53)).
+
+
+**Review/Mentoring contributions:**
+
+* Reviewed a total of 31 PRs.
+* Reviewed and offered suggestions for team member's pull requests.
+
+
+**Contributions beyond the team project:**
+
+* Assisted other teams and reported 9 bugs during the [Practical Exam Dry Run](https://github.com/jovkusuma/ped/issues).
diff --git a/docs/team/lambraydon.md b/docs/team/lambraydon.md
new file mode 100644
index 00000000000..eb70ca3655b
--- /dev/null
+++ b/docs/team/lambraydon.md
@@ -0,0 +1,58 @@
+---
+layout: page
+title: Lam Jin Heng Braydon's Project Portfolio Page
+---
+
+### Project: TuitionConnect
+
+TuitionConnect is a desktop app built for tutors and tutoring businesses to simplify the process of administration and finance management, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI).
+
+Given below are my contributions to the project.
+
+* **New Feature**: `ListByDayCommand`
+ * What it does: Shows the current list of tutees who have lessons on a specified day
+ * Justification: This feature allows private tutors to be reminded if they have any classes on that particular day with a single command
+ * Highlights: This feature extends the `ListCommand` class and uses the same command syntax `list` followed by an optional `[DAY]` parameter. If the user does not specify the `[DAY]` parameter, it will be parsed as a `ListCommand` and shows all tutees in the list. If the `[DAY]`
+ parameter is included, it will be parsed as a `ListByDayCommand`
+
+* **New Feature**: `Undo`
+ * What it does: Undo commands that alter the data of tutees such as `delete`, `add` and `edit`
+ * Justification: This feature allows private tutors to efficiently undo commands if they made a mistake.
+ * Highlights: This feature saves the state of the `VersionedAddressBook` after every command that alters the data of tutees into temporary memory which will be cleared everytime the application is closed
+
+* **New Feature**: `Redo`
+ * What it does: Redo undo commands that alter the data of tutees such as `delete`, `add` and `edit`
+ * Justification: This feature allows private tutors to efficiently redo commands if they made a mistake.
+
+* **New Feature**: Created a `ScheduleList` that is displayed on the UI as a `ScheduleListPanel` which allows tutor to view upcoming lessons.
+ * What it does: Sorts a private tutor's lessons according the day and time so in an `ObservableList`.
+ * Justification: This feature allows private tutors to easily glance their schedule for the week.
+ * Highlights: This features requires a `Lesson` Class that encapsulates the `Day`, `Begin` and `End` fields in an `add` command. `Lesson` objects are than sorted using a `LessonComparator`
+
+* **Code contributed**:
+ * [Reposense Link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=lambraydon&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&zFR=false&tabType=authorship&tabAuthor=lambraydon&tabRepo=AY2324S1-CS2103T-F10-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false)
+
+* **Project management**:
+ * Set up codecov for team repo
+ * Created team organisation on github
+ * Created initial UI mockup in v1.2
+
+* **Enhancements to existing features**:
+ * Refactored code, added `Day` parameter (Pull requests [\#70](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/70))
+ * Updated GUI by modifying UI of list tiles (Pull requests [\#117](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/117))
+ * Refactored `Person#isSamePerson` such that a person is considered a duplicate if they have the `Name` and `Phone` (Pull requests [\#126](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/126))
+
+* **Documentation**:
+ * User Guide:
+ * Added and modified documentation for the features `list` , `undo` and `redo` features (Pull requests [\#217](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/217))
+ * Added parameters requirement section
+ * Developer Guide:
+ * Added implementation details and sequence diagrams of the `list`, `undo`, `redo` and `add` feature. (Pull requests [\#188](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/188), [\#187](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/187), [\#201](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/201))
+ * Added planned enhancements (Pull requests [\#226](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/226))
+ * Added manual test cases for `undo`, `redo` and `list`(Pull requests [\#226](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/226))
+
+* **Community**:
+ * Assisted other teams in finding bugs during the Practical Exam Dry Run
+
+* **Tools**:
+ * codeCov
diff --git a/docs/team/winstonleonard.md b/docs/team/winstonleonard.md
new file mode 100644
index 00000000000..880ab89ebdc
--- /dev/null
+++ b/docs/team/winstonleonard.md
@@ -0,0 +1,74 @@
+---
+layout: page
+title: Winston Leonard Prayonggo's Project Portfolio Page
+---
+
+### Project: TuitionConnect
+TuitionConnect is a **desktop app** built for tutors and tutoring businesses to simplify the process of
+administration and finance management, optimized for use via a **Command Line Interface** (CLI) while
+still having the benefits of a Graphical User Interface (GUI).
+
+
+**Code contributed:** Click here for [RepoSense Link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=winstonleonard&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&tabAuthor=WinstonLeonard&tabRepo=AY2324S1-CS2103T-F10-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false)
+
+
+**Enhancements implemented:**
+
+
+ * Refactored the `add` and `edit` command.
+ * Allowed tutees to take in `Subject`, `Day`, `Begin`, `End` fields.
+ * (Pull Requests [#72](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/72))
+
+
+ * Added clashing schedules mechanism
+ * Prevents the `add` and `edit` command from adding or editing tutees that will result in clashing schedules.
+ * (Pull Requests [#93](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/93))
+
+
+ * Implemented the `freeTime` command
+ * Command that finds free time in the user's schedule.
+ * (Pull Requests [#121](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/121))
+
+
+ * Write and fixing test cases:
+ * Fixed the test cases for `AddCommandTest`, `EditCommandTest`, `AddCommandParserTest`, `EditCommandParserTest`, `AddressBookParserTest`. `PersonBuilder`, `PersonUtil`, `TypicalPerson`
+ to support refactored `add` and `edit` command. (Pull Requests [#73](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/73))
+ * Wrote test cases for `DurationTest`, `IntervalTest`, `IntervalBeginTest`, `IntervalEndTest`, `IntervalDayTest`, `TimeSlotTest`, `FreeTimeCommandTest`, `FreeTimeCommandParserTest`
+ to test the `freeTime` Command. (Pull Requests [#121](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/121))
+ * Wrote test cases for clashing schedules for `add` and `edit` command (Pull Requests [#198](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/198))
+
+
+**Contributions to the UG:**
+
+
+ * Added Welcome Section, Introduction Section, Symbol Syntax Table. (Pull Requests [#207](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/207))
+ * Added the Layout Section (Pull Requests [#216](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/216))
+ * Wrote descriptions for `add` and `freeTime` (Pull Requests [#221](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/221))
+
+
+**Contributions to the DG:**
+
+
+* Modified Model Class Diagram. (Pull Requests [#192](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/192))
+* Added description and Sequence Diagram for `freeTime` command (Pull Requests [#192](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/192))
+* Added Activity Diagrams for `freeTime` and `edit` command (Pull Requests [#211](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/211))
+* Wrote manual test cases for `add`, `freeTime`, `edit`, and also added a glossary. (Pull requests [#244](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/224))
+* Wrote planned enhancements (Pull Requests [#232](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/232))
+
+
+**Contributions to team-based tasks**:
+
+
+* Enabling assertions (Pull Requests [#104](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/104))
+
+
+**Review/Mentoring contributions:**
+
+
+* Reviewed and offered suggestions for team member's pull requests.
+
+
+**Contributions beyond the team project:**
+
+
+* Assisted other teams in finding bugs during the [Practical Exam Dry Run](https://github.com/WinstonLeonard/ped/issues)
diff --git a/docs/team/yihfei.md b/docs/team/yihfei.md
new file mode 100644
index 00000000000..3bf10e92678
--- /dev/null
+++ b/docs/team/yihfei.md
@@ -0,0 +1,58 @@
+---
+layout: page
+title: Lim Yih Fei's Project Portfolio Page
+---
+### Project: TuitionConnect
+TuitionConnect is a **desktop app** built for tutors and tutoring businesses to simplify the process of
+administration and finance management, optimized for use via a **Command Line Interface** (CLI) while
+still having the benefits of a Graphical User Interface (GUI).
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability to calculate total monthly revenue. (Pull requests
+[\#88](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/88),
+[\#112](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/112),
+[\#114](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/114))
+ * What it does: allows the user to see a monthly revenue figure.
+ * Justification: This feature allows private tutors to financially plan better.
+ * Highlights: This feature accounts for different number of days in a month for calculation of revenue.
+
+* **New Feature**: Added a `ScheduleListPanel` to see a summarised view of upcoming lessons. (Pull requests
+[\#71](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/71),
+[\#115](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/115))
+ * What it does: allows the user to quickly see a list of lesson cards that is sorted sequentially from the current day.
+ * Justification: This feature allows private tutors to easily plan for their schedules.
+ * Highlights: This features requires an enhancement of lesson comparator to sort the lesson cards.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=yihfei&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&zFR=false&tabType=authorship&tabAuthor=yihfei&tabRepo=AY2324S1-CS2103T-F10-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false)
+
+* **Project management**:
+ * Managed releases for jar files (trial v1.3 and v1.3 for Mock PE)
+ * Maintained the team collaboration google docs (e.g. documenting features for v1.3)
+ * Renamed the product (Pull request [\#118](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/118))
+ * Created the icon for product (Pull request [\#118](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/118))
+ * Drafted the mock UI
+
+* **Enhancements to existing features**:
+ * Enhanced the GUI (Implement coloured tags for paid indicator, tags for subject, rounded corners for result display and search bar) (Pull requests
+[\#118](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/118)
+[\#124](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/124))
+ * Wrote additional tests for lesson class and RevenueCommand related features (Pull requests
+[\#112](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/112)
+[\#222](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/222))
+
+* **Documentation**:
+ * User Guide and Developer Guide:
+ * Added and modified documentation for the features `add` , `edit` and `rev` features (Pull requests
+[\#131](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/131),
+[\#189](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/189),
+[\#194](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/194),
+[\#233](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/233))
+ * Updated GUI pictures
+ * Added implementation details and sequence diagrams of the `add` and `rev` features (Pull requests
+[\#189](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/189),
+[\#194](https://github.com/AY2324S1-CS2103T-F10-4/tp/pull/194))
+
+* **Community**:
+ * Participated in PE Dry run
+
diff --git a/hello.txt b/hello.txt
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 3d6bd06d5af..76381ef72ec 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -35,8 +35,9 @@
* Runs the application.
*/
public class MainApp extends Application {
+ // initializes Ui, logic and storage for GUI
- public static final Version VERSION = new Version(0, 2, 2, true);
+ public static final Version VERSION = new Version(1, 3, 1, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java
index 61cc8c9a1cb..60039f3821a 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/address/commons/util/StringUtil.java
@@ -18,7 +18,7 @@ public class StringUtil {
* examples:
* containsWordIgnoreCase("ABc def", "abc") == true
* containsWordIgnoreCase("ABc def", "DEF") == true
- * containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
+ * containsWordIgnoreCase("ABc def", "AB") == true //Partial name search
*
* @param sentence cannot be null
* @param word cannot be null, cannot be empty, must be a single word
@@ -27,15 +27,15 @@ public static boolean containsWordIgnoreCase(String sentence, String word) {
requireNonNull(sentence);
requireNonNull(word);
- String preppedWord = word.trim();
+ String preppedWord = word.toLowerCase().trim();
checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty");
checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word");
- String preppedSentence = sentence;
+ String preppedSentence = sentence.toLowerCase();
String[] wordsInPreppedSentence = preppedSentence.split("\\s+");
return Arrays.stream(wordsInPreppedSentence)
- .anyMatch(preppedWord::equalsIgnoreCase);
+ .anyMatch(x -> x.contains(preppedWord));
}
/**
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..92222f119a1 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -33,6 +33,9 @@ public interface Logic {
/** Returns an unmodifiable view of the filtered list of persons */
ObservableList getFilteredPersonList();
+ /** Returns an unmodifiable view of the sorted list of persons by dateTime */
+ ObservableList getScheduleList();
+
/**
* Returns the user prefs' address book file path.
*/
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
index 5aa3b91c7d0..3db1d3a94a6 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -71,6 +71,11 @@ public ObservableList getFilteredPersonList() {
return model.getFilteredPersonList();
}
+ @Override
+ public ObservableList getScheduleList() {
+ return model.getScheduleList();
+ }
+
@Override
public Path getAddressBookFilePath() {
return model.getAddressBookFilePath();
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java
index ecd32c31b53..a9f772d8d6a 100644
--- a/src/main/java/seedu/address/logic/Messages.java
+++ b/src/main/java/seedu/address/logic/Messages.java
@@ -14,10 +14,15 @@ 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_BEGIN_AFTER_END = "Begin time must be before End time.";
+ public static final String MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX = "The tutee index provided is invalid";
+ public static final String MESSAGE_TUTEES_LISTED_OVERVIEW = "%1$d tutees listed!";
public static final String MESSAGE_DUPLICATE_FIELDS =
"Multiple values specified for the following single-valued field(s): ";
+ public static final String MESSAGE_CANNOT_UNDO =
+ "Nothing to undo!";
+ public static final String MESSAGE_CANNOT_REDO =
+ "Nothing to redo!";
/**
* Returns an error message indicating the duplicate prefixes.
@@ -41,10 +46,14 @@ public static String format(Person person) {
.append(person.getPhone())
.append("; Email: ")
.append(person.getEmail())
+ .append("; IsPaid: ")
+ .append(person.getPaid())
.append("; Address: ")
.append(person.getAddress())
- .append("; Tags: ");
- person.getTags().forEach(builder::append);
+ .append("; Day: ")
+ .append(person.getDay())
+ .append("; PayRate: ")
+ .append(person.getPayRate());
return builder.toString();
}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 5d7185a9680..c628eb16abc 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddCommand.java
@@ -1,16 +1,25 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.Messages.MESSAGE_BEGIN_AFTER_END;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
+import seedu.address.model.person.Lesson;
import seedu.address.model.person.Person;
/**
@@ -26,20 +35,32 @@ public class AddCommand extends Command {
+ PREFIX_PHONE + "PHONE "
+ PREFIX_EMAIL + "EMAIL "
+ PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
- + "Example: " + COMMAND_WORD + " "
+ + PREFIX_SUBJECT + "SUBJECT "
+ + PREFIX_DAY + "DAY "
+ + PREFIX_BEGIN + "START "
+ + PREFIX_END + "END "
+ + PREFIX_PAYRATE + "PAYRATE"
+ + "\nExample: " + COMMAND_WORD + " "
+ PREFIX_NAME + "John Doe "
+ PREFIX_PHONE + "98765432 "
+ PREFIX_EMAIL + "johnd@example.com "
+ PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
- + PREFIX_TAG + "friends "
- + PREFIX_TAG + "owesMoney";
+ + PREFIX_SUBJECT + "Maths "
+ + PREFIX_DAY + "Mon "
+ + PREFIX_BEGIN + "1200 "
+ + PREFIX_END + "1300 "
+ + PREFIX_PAYRATE + "20.00";
public static final String MESSAGE_SUCCESS = "New person added: %1$s";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
+
+ public static final String MESSAGE_DUPLICATE_PERSON = "This tutee already exists";
+
+ public static final String MESSAGE_DUPLICATE_DATE = "This date and time clashes with an existing schedule";
private final Person toAdd;
+ private final Logger logger = LogsCenter.getLogger(getClass());
+
/**
* Creates an AddCommand to add the specified {@code Person}
*/
@@ -52,11 +73,25 @@ public AddCommand(Person person) {
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
+ if (!Lesson.isValid(toAdd.getBegin(), toAdd.getEnd())) {
+ logger.info("[AddCommand.execute()]: Begin after End");
+ throw new CommandException(String.format(MESSAGE_BEGIN_AFTER_END, AddCommand.MESSAGE_USAGE));
+ }
+
if (model.hasPerson(toAdd)) {
+ logger.info("[AddCommand.execute()]: Duplicate Person");
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
+ if (model.hasDate(toAdd)) {
+ logger.info("[AddCommand.execute()]: Clashing Schedules");
+ throw new CommandException(MESSAGE_DUPLICATE_DATE);
+ }
+
+ model.purgeAddressBook();
model.addPerson(toAdd);
+ model.commitAddressBook();
+
return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd)));
}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
index 9c86b1fa6e4..4cea8ecdf36 100644
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java
@@ -11,13 +11,17 @@
public class ClearCommand extends Command {
public static final String COMMAND_WORD = "clear";
- public static final String MESSAGE_SUCCESS = "Address book has been cleared!";
+ public static final String MESSAGE_SUCCESS = "Tutee data has been cleared!";
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
+
+ model.purgeAddressBook();
model.setAddressBook(new AddressBook());
+ model.commitAddressBook();
+
return new CommandResult(MESSAGE_SUCCESS);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
index 1135ac19b74..547fbd28b63 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
@@ -37,11 +37,15 @@ public CommandResult execute(Model model) throws CommandException {
List lastShownList = model.getFilteredPersonList();
if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
}
Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
+
+ model.purgeAddressBook();
model.deletePerson(personToDelete);
+ model.commitAddressBook();
+
return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete)));
}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
index 4b581c7331e..3dca6faa746 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditCommand.java
@@ -1,20 +1,25 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.Messages.MESSAGE_BEGIN_AFTER_END;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PAID;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
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.Objects;
import java.util.Optional;
-import java.util.Set;
+import java.util.logging.Logger;
+import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.CollectionUtil;
import seedu.address.commons.util.ToStringBuilder;
@@ -22,11 +27,16 @@
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Begin;
+import seedu.address.model.person.Day;
import seedu.address.model.person.Email;
+import seedu.address.model.person.End;
+import seedu.address.model.person.Lesson;
import seedu.address.model.person.Name;
+import seedu.address.model.person.PayRate;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Subject;
/**
* Edits the details of an existing person in the address book.
@@ -43,17 +53,25 @@ public class EditCommand extends Command {
+ "[" + PREFIX_PHONE + "PHONE] "
+ "[" + PREFIX_EMAIL + "EMAIL] "
+ "[" + PREFIX_ADDRESS + "ADDRESS] "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + "[" + PREFIX_SUBJECT + "SUBJECT] "
+ + "[" + PREFIX_DAY + "DAY] "
+ + "[" + PREFIX_BEGIN + "BEGIN] "
+ + "[" + PREFIX_END + "END] "
+ + "[" + PREFIX_PAID + "PAID]...\n"
+ + "[" + PREFIX_PAYRATE + "PAYRATE]...\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.";
+ public static final String MESSAGE_DUPLICATE_PERSON = "This tutee already exists";
+ public static final String MESSAGE_DUPLICATE_DATE = "This date clashes with an existing schedule";
private final Index index;
private final EditPersonDescriptor editPersonDescriptor;
+ private final Logger logger = LogsCenter.getLogger(getClass());
+
/**
* @param index of the person in the filtered person list to edit
@@ -73,18 +91,41 @@ public CommandResult execute(Model model) throws CommandException {
List lastShownList = model.getFilteredPersonList();
if (index.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
}
Person personToEdit = lastShownList.get(index.getZeroBased());
Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
+ if (!Lesson.isValid(editedPerson.getBegin(), editedPerson.getEnd())) {
+ throw new CommandException(String.format(MESSAGE_BEGIN_AFTER_END, AddCommand.MESSAGE_USAGE));
+ }
+
+ if (editedPerson.getBegin() == null) {
+ if (!Lesson.isValid(personToEdit.getBegin(), editedPerson.getEnd())) {
+ throw new CommandException(String.format(MESSAGE_BEGIN_AFTER_END, AddCommand.MESSAGE_USAGE));
+ }
+ }
+
+ if (editedPerson.getEnd() == null) {
+ if (!Lesson.isValid(editedPerson.getBegin(), personToEdit.getEnd())) {
+ throw new CommandException(String.format(MESSAGE_BEGIN_AFTER_END, AddCommand.MESSAGE_USAGE));
+ }
+ }
+
if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
+ logger.info("[EditCommand.execute()]: Editing would result in duplicate persons");
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
+ } else if (editPersonDescriptor.getEditSchedule() && model.hasDate(editedPerson)) {
+ logger.info("[EditCommand.execute()]: Editing would result in clashing schedules");
+ throw new CommandException(MESSAGE_DUPLICATE_DATE);
}
- model.setPerson(personToEdit, editedPerson);
+ model.setPerson(personToEdit, editedPerson, editPersonDescriptor.getEditSchedule());
+ model.purgeAddressBook();
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.commitAddressBook();
+
return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)));
}
@@ -99,9 +140,15 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript
Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
- Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
-
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ Subject updatedSubject = editPersonDescriptor.getSubject().orElse(personToEdit.getSubject());
+ Day updatedDay = editPersonDescriptor.getDay().orElse(personToEdit.getDay());
+ Begin updatedBegin = editPersonDescriptor.getBegin().orElse(personToEdit.getBegin());
+ End updatedEnd = editPersonDescriptor.getEnd().orElse(personToEdit.getEnd());
+ Boolean updatedPaid = editPersonDescriptor.getPaid().orElse(personToEdit.getPaid());
+ PayRate updatedPayRate = editPersonDescriptor.getPayRate().orElse(personToEdit.getPayRate());
+
+ return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedSubject, updatedDay,
+ updatedBegin, updatedEnd, updatedPaid, updatedPayRate);
}
@Override
@@ -137,7 +184,13 @@ public static class EditPersonDescriptor {
private Phone phone;
private Email email;
private Address address;
- private Set tags;
+ private Subject subject;
+ private Day day;
+ private Begin begin;
+ private End end;
+ private Boolean paid;
+ private PayRate payRate;
+ private Boolean editSchedule = false;
public EditPersonDescriptor() {}
@@ -150,14 +203,20 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setPhone(toCopy.phone);
setEmail(toCopy.email);
setAddress(toCopy.address);
- setTags(toCopy.tags);
+ setSubject(toCopy.subject);
+ setDay(toCopy.day);
+ setBegin(toCopy.begin);
+ setEnd(toCopy.end);
+ setPaid(toCopy.paid);
+ setPayRate(toCopy.payRate);
+ setEditSchedule(toCopy.editSchedule);
}
/**
* Returns true if at least one field is edited.
*/
public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
+ return CollectionUtil.isAnyNonNull(name, phone, email, address, subject, day, begin, end, paid, payRate);
}
public void setName(Name name) {
@@ -192,21 +251,59 @@ public Optional getAddress() {
return Optional.ofNullable(address);
}
- /**
- * Sets {@code tags} to this object's {@code tags}.
- * A defensive copy of {@code tags} is used internally.
- */
- public void setTags(Set tags) {
- this.tags = (tags != null) ? new HashSet<>(tags) : null;
+ public void setSubject(Subject subject) {
+ this.subject = subject;
}
- /**
- * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- * Returns {@code Optional#empty()} if {@code tags} is null.
- */
- public Optional> getTags() {
- return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
+ public Optional getSubject() {
+ return Optional.ofNullable(subject);
+ }
+
+ public void setDay(Day day) {
+ this.day = day;
+ }
+
+ public Optional getDay() {
+ return Optional.ofNullable(day);
+ }
+
+ public void setBegin(Begin begin) {
+ this.begin = begin;
+ }
+
+ public Optional getBegin() {
+ return Optional.ofNullable(begin);
+ }
+
+ public void setEnd(End end) {
+ this.end = end;
+ }
+
+ public Optional getEnd() {
+ return Optional.ofNullable(end);
+ }
+
+ public void setPaid(Boolean paid) {
+ this.paid = paid;
+ }
+
+ public Optional getPaid() {
+ return Optional.ofNullable(paid);
+ }
+
+ public void setPayRate(PayRate payRate) {
+ this.payRate = payRate;
+ }
+
+ public Optional getPayRate() {
+ return Optional.ofNullable(payRate);
+ }
+ public void setEditSchedule(Boolean setTrue) {
+ this.editSchedule = setTrue;
+ }
+
+ public Boolean getEditSchedule() {
+ return this.editSchedule;
}
@Override
@@ -225,7 +322,13 @@ public boolean equals(Object other) {
&& Objects.equals(phone, otherEditPersonDescriptor.phone)
&& Objects.equals(email, otherEditPersonDescriptor.email)
&& Objects.equals(address, otherEditPersonDescriptor.address)
- && Objects.equals(tags, otherEditPersonDescriptor.tags);
+ && Objects.equals(subject, otherEditPersonDescriptor.subject)
+ && Objects.equals(day, otherEditPersonDescriptor.day)
+ && Objects.equals(begin, otherEditPersonDescriptor.begin)
+ && Objects.equals(end, otherEditPersonDescriptor.end)
+ && Objects.equals(paid, otherEditPersonDescriptor.paid)
+ && Objects.equals(payRate, otherEditPersonDescriptor.payRate);
+
}
@Override
@@ -235,7 +338,12 @@ public String toString() {
.add("phone", phone)
.add("email", email)
.add("address", address)
- .add("tags", tags)
+ .add("day", day)
+ .add("begin", begin)
+ .add("end", end)
+ .add("paid", paid)
+ .add("payrate", payRate)
+ .add("editSchedule", editSchedule)
.toString();
}
}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
index 72b9eddd3a7..dc60ba54cb7 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -1,39 +1,66 @@
package seedu.address.logic.commands;
import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.NameSubjectPredicate;
+import seedu.address.model.person.SubjectContainsKeywordsPredicate;
/**
* Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
+ * Keyword matching is case-insensitive.
*/
public class FindCommand extends Command {
public static final String COMMAND_WORD = "find";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
- + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
- + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
- + "Example: " + COMMAND_WORD + " alice bob charlie";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Find persons with names or subjects "
+ + "matching the specified keywords (case-insensitive).\n"
+ + "Parameters: " + PREFIX_NAME + "[NAME_KEYWORD] " + PREFIX_SUBJECT + "[SUBJECT_KEYWORD]\n"
+ + "Examples: \n"
+ + "1. " + COMMAND_WORD + " " + PREFIX_NAME + "Alice " + PREFIX_SUBJECT + "Maths \n"
+ + "2. " + COMMAND_WORD + " " + PREFIX_NAME + "Alice \n"
+ + "3. " + COMMAND_WORD + " " + PREFIX_SUBJECT + "Maths";
+
+ private static final Logger logger = LogsCenter.getLogger(FindCommand.class);
private final NameContainsKeywordsPredicate predicate;
+ private final SubjectContainsKeywordsPredicate subject;
- public FindCommand(NameContainsKeywordsPredicate predicate) {
+ /**
+ * Constructore for the findCommand class
+ *
+ * @param predicate the keyword to be searched which starts with the prefix n/
+ * @param subject the keyword to be searched which starts with the prefix sb/
+ */
+ public FindCommand(NameContainsKeywordsPredicate predicate, SubjectContainsKeywordsPredicate subject) {
+ requireAllNonNull(predicate, subject);
this.predicate = predicate;
+ this.subject = subject;
}
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
- model.updateFilteredPersonList(predicate);
+ NameSubjectPredicate nameSubject = new NameSubjectPredicate(predicate, subject);
+ model.updateFilteredPersonList(nameSubject);
+
+ logger.log(Level.INFO, "FindCommand executed with predicate: " + predicate + "and subject:" + subject);
+
return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ String.format(Messages.MESSAGE_TUTEES_LISTED_OVERVIEW,
+ model.getFilteredPersonList().size()));
}
-
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/logic/commands/FreeTimeCommand.java b/src/main/java/seedu/address/logic/commands/FreeTimeCommand.java
new file mode 100644
index 00000000000..f8e542140dc
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/FreeTimeCommand.java
@@ -0,0 +1,75 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END;
+
+import java.text.ParseException;
+import java.util.List;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.interval.Interval;
+import seedu.address.model.interval.TimeSlot;
+
+/**
+ * FreeTimeCommand
+ */
+public class FreeTimeCommand extends Command {
+
+ public static final String COMMAND_WORD = "freeTime";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": finds a free time. "
+ + "Parameters: "
+ + PREFIX_DAY + "DAY "
+ + PREFIX_DURATION + "MINUTES "
+ + PREFIX_BEGIN + "BEGIN "
+ + PREFIX_END + "END "
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_DAY + "Monday "
+ + PREFIX_DURATION + "60 "
+ + PREFIX_BEGIN + "0800 "
+ + PREFIX_END + "2200 ";
+
+ public static final String MESSAGE_SUCCESS = "Here is your list of free time:\n%s";
+ public static final String MESSAGE_ERROR = "An error occurred when executing the freeTime command.";
+ private final Interval toFind;
+
+ /**
+ * Creates an FreeTimeCommand to check for the specified {@code Interval}
+ */
+ public FreeTimeCommand(Interval interval) {
+ requireNonNull(interval);
+ toFind = interval;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List result = model.findInterval(toFind);
+ try {
+ List timeslots = TimeSlot.parseIntervals(result);
+ List availableTime = TimeSlot.findAvailableTime(timeslots, toFind);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, TimeSlot.printResults(availableTime)));
+ } catch (ParseException e) {
+ throw new CommandException(MESSAGE_ERROR);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof FreeTimeCommand)) {
+ return false;
+ }
+
+ FreeTimeCommand otherFindCommand = (FreeTimeCommand) other;
+ return toFind.equals(otherFindCommand.toFind);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/IsPaidCommand.java b/src/main/java/seedu/address/logic/commands/IsPaidCommand.java
new file mode 100644
index 00000000000..82c651f1902
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/IsPaidCommand.java
@@ -0,0 +1,68 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+/**
+ * Check if the tutee is paid.
+ * (Make use of the template of Delete Command and did some modification)
+ */
+public class IsPaidCommand extends Command {
+ public static final String COMMAND_WORD = "ispaid";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_CHECK_PERSON_PAID = "Check Whether Person Paid, Paid: %1$s";
+
+ private final Index targetIndex;
+
+ public IsPaidCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
+ }
+
+ Person personToGetPaid = lastShownList.get(targetIndex.getZeroBased());
+ model.getPersonPaid(personToGetPaid);
+ return new CommandResult(String.format(MESSAGE_CHECK_PERSON_PAID, personToGetPaid.getPaid()));
+
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof IsPaidCommand)) {
+ return false;
+ }
+
+ IsPaidCommand otherPaidCommands = (IsPaidCommand) other;
+ return targetIndex.equals(otherPaidCommands.targetIndex);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("targetIndex", targetIndex)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListByDayCommand.java b/src/main/java/seedu/address/logic/commands/ListByDayCommand.java
new file mode 100644
index 00000000000..e9ae2ee0674
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListByDayCommand.java
@@ -0,0 +1,41 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.person.DayPredicate;
+
+/**
+ * Lists all persons in the address book to the user by day.
+ */
+public class ListByDayCommand extends ListCommand {
+ private final DayPredicate predicate;
+
+ public ListByDayCommand(DayPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredPersonList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_TUTEES_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ListByDayCommand)) {
+ return false;
+ }
+
+ ListByDayCommand otherListByDayCommand = (ListByDayCommand) other;
+ return predicate.equals(otherListByDayCommand.predicate);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
index 84be6ad2596..89f33857939 100644
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ListCommand.java
@@ -5,15 +5,18 @@
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 tutees";
- public static final String MESSAGE_SUCCESS = "Listed all persons";
-
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all tutees or list tutees filtered by day.\n "
+ + "Parameters: DAY (OPTIONAL).\n"
+ + "Example: " + COMMAND_WORD + " Monday";
@Override
public CommandResult execute(Model model) {
diff --git a/src/main/java/seedu/address/logic/commands/ListUnPaidCommand.java b/src/main/java/seedu/address/logic/commands/ListUnPaidCommand.java
new file mode 100644
index 00000000000..db8befc7778
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListUnPaidCommand.java
@@ -0,0 +1,49 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.person.PaidPredicate;
+
+/**
+ * Lists all unpaid persons.
+ */
+public class ListUnPaidCommand extends ListCommand {
+ private final PaidPredicate predicate;
+
+ public ListUnPaidCommand(PaidPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredPersonList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_TUTEES_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ListUnPaidCommand)) {
+ return false;
+ }
+
+ ListUnPaidCommand otherListUnPaidCommand = (ListUnPaidCommand) other;
+ return predicate.equals(otherListUnPaidCommand.predicate);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("predicate", predicate)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/PaidCommand.java b/src/main/java/seedu/address/logic/commands/PaidCommand.java
new file mode 100644
index 00000000000..a1d4b57c72b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/PaidCommand.java
@@ -0,0 +1,71 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+/**
+ * Mark the specific tutee as paid.
+ * (Make use of the template of Delete Command and did some modification)
+ */
+public class PaidCommand extends Command {
+ public static final String COMMAND_WORD = "paid";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_MARK_PERSON_PAID_SUCCESS = "MARK PERSON PAID SUCCESS";
+
+ private final Index targetIndex;
+
+ public PaidCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
+ }
+
+ Person personToMarkPaid = lastShownList.get(targetIndex.getZeroBased());
+
+ model.purgeAddressBook();
+ model.markPersonPaid(personToMarkPaid);
+ model.commitAddressBook();
+
+ return new CommandResult(String.format(MESSAGE_MARK_PERSON_PAID_SUCCESS, personToMarkPaid.getPaid()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof PaidCommand)) {
+ return false;
+ }
+
+ PaidCommand otherPaidCommands = (PaidCommand) other;
+ return targetIndex.equals(otherPaidCommands.targetIndex);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("targetIndex", targetIndex)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java
new file mode 100644
index 00000000000..9703c1ce5d3
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java
@@ -0,0 +1,28 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Redo the Address Book to the previous undoned saved version
+ */
+public class RedoCommand extends Command {
+ public static final String COMMAND_WORD = "redo";
+ public static final String MESSAGE_SUCCESS = "Successfully redo previous command";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (!model.canRedoAddressBook()) {
+ throw new CommandException(Messages.MESSAGE_CANNOT_REDO);
+ }
+
+ model.redoAddressBook();
+
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/RevenueCommand.java b/src/main/java/seedu/address/logic/commands/RevenueCommand.java
new file mode 100644
index 00000000000..539babb9e64
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/RevenueCommand.java
@@ -0,0 +1,39 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+/**
+ * Calculates the total monthly revenue from all tutees.
+ */
+public class RevenueCommand extends Command {
+
+ public static final String COMMAND_WORD = "rev";
+
+ public static final String MESSAGE_SUCCESS = "Successfully calculated" + "\nTotal monthly revenue: $";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Calculates total revenue "
+ + "earned from all tutees in a month.\n " + "Example: " + COMMAND_WORD;
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ List fullList = model.getUnfilteredPersonList();
+
+ double totalRevenue = 0;
+ // for every person, find revenue, add them tgt
+ for (Person tutee : fullList) {
+ // get revenue per lesson
+ double individualMonthlyFee = tutee.getMonthlyFee();
+ assert individualMonthlyFee >= 0 : "monthly fee cannot be negative";
+ totalRevenue += individualMonthlyFee;
+ }
+
+ String formattedTotalRevenue = String.format("%.2f", totalRevenue);
+ return new CommandResult(MESSAGE_SUCCESS + formattedTotalRevenue);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UnPaidAllCommand.java b/src/main/java/seedu/address/logic/commands/UnPaidAllCommand.java
new file mode 100644
index 00000000000..91f68f5aec4
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UnPaidAllCommand.java
@@ -0,0 +1,38 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+
+import java.util.List;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+/**
+ * Mark All tutees in the current list as unpaid.
+ */
+public class UnPaidAllCommand extends Command {
+ public static final String COMMAND_WORD = "unpaidAll";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD;
+
+ public static final String MESSAGE_MARK_ALL_PERSON_UNPAID_SUCCESS = "RESET ALL PERSON TO UNPAID SUCCESS";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ model.purgeAddressBook();
+
+ for (Person person : lastShownList) {
+ model.markPersonUnPaid(person);
+ }
+
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ model.commitAddressBook();
+
+ return new CommandResult(MESSAGE_MARK_ALL_PERSON_UNPAID_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UnPaidCommand.java b/src/main/java/seedu/address/logic/commands/UnPaidCommand.java
new file mode 100644
index 00000000000..3aa336974a5
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UnPaidCommand.java
@@ -0,0 +1,71 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+
+/**
+ * Mark the tutee as unpaid.
+ * (Make use of the template of Delete Command and did some modification)
+ */
+public class UnPaidCommand extends Command {
+ public static final String COMMAND_WORD = "unpaid";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_MARK_PERSON_UNPAID_SUCCESS = "MARK PERSON UNPAID SUCCESS";
+
+ private final Index targetIndex;
+
+ public UnPaidCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
+ }
+
+ Person personToMarkUnPaid = lastShownList.get(targetIndex.getZeroBased());
+
+ model.purgeAddressBook();
+ model.markPersonUnPaid(personToMarkUnPaid);
+ model.commitAddressBook();
+
+ return new CommandResult(String.format(MESSAGE_MARK_PERSON_UNPAID_SUCCESS, (personToMarkUnPaid.getPaid())));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof UnPaidCommand)) {
+ return false;
+ }
+
+ UnPaidCommand otherUnPaidCommands = (UnPaidCommand) other;
+ return targetIndex.equals(otherUnPaidCommands.targetIndex);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("targetIndex", targetIndex)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java
new file mode 100644
index 00000000000..65e06e1c91d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java
@@ -0,0 +1,28 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+
+/**
+ * Undo the Address Book to the previous saved version
+ */
+public class UndoCommand extends Command {
+ public static final String COMMAND_WORD = "undo";
+ public static final String MESSAGE_SUCCESS = "Successfully undo previous command";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (!model.canUndoAddressBook()) {
+ throw new CommandException(Messages.MESSAGE_CANNOT_UNDO);
+ }
+
+ model.undoAddressBook();
+
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 4ff1a97ed77..91a98eb7569 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -2,23 +2,29 @@
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
-import java.util.Set;
import java.util.stream.Stream;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Begin;
+import seedu.address.model.person.Day;
import seedu.address.model.person.Email;
+import seedu.address.model.person.End;
import seedu.address.model.person.Name;
+import seedu.address.model.person.PayRate;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
+import seedu.address.model.person.Subject;
/**
* Parses input arguments and creates a new AddCommand object
*/
@@ -31,21 +37,29 @@ public class AddCommandParser implements Parser {
*/
public AddCommand parse(String args) throws ParseException {
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_SUBJECT, PREFIX_DAY, PREFIX_BEGIN, PREFIX_END, PREFIX_PAYRATE);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_SUBJECT,
+ PREFIX_DAY, PREFIX_BEGIN, PREFIX_END, PREFIX_PAYRATE)
|| !argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_SUBJECT, PREFIX_DAY, PREFIX_BEGIN, PREFIX_END, PREFIX_PAYRATE);
Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
- Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+ Subject subject = ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT).get());
+ Day day = ParserUtil.parseDay(argMultimap.getValue(PREFIX_DAY).get());
+ Begin begin = ParserUtil.parseBegin(argMultimap.getValue(PREFIX_BEGIN).get());
+ End end = ParserUtil.parseEnd(argMultimap.getValue(PREFIX_END).get());
+ PayRate payRate = ParserUtil.parsePayRate(argMultimap.getValue(PREFIX_PAYRATE).get());
+ boolean paid = false;
- Person person = new Person(name, phone, email, address, tagList);
+ Person person = new Person(name, phone, email, address, subject, day, begin, end, paid, payRate);
return new AddCommand(person);
}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 3149ee07e0b..9798995e4dc 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -15,8 +15,16 @@
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.FreeTimeCommand;
import seedu.address.logic.commands.HelpCommand;
+import seedu.address.logic.commands.IsPaidCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.PaidCommand;
+import seedu.address.logic.commands.RedoCommand;
+import seedu.address.logic.commands.RevenueCommand;
+import seedu.address.logic.commands.UnPaidAllCommand;
+import seedu.address.logic.commands.UnPaidCommand;
+import seedu.address.logic.commands.UndoCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -69,7 +77,7 @@ public Command parseCommand(String userInput) throws ParseException {
return new FindCommandParser().parse(arguments);
case ListCommand.COMMAND_WORD:
- return new ListCommand();
+ return new ListCommandParser().parse(arguments);
case ExitCommand.COMMAND_WORD:
return new ExitCommand();
@@ -77,10 +85,33 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case PaidCommand.COMMAND_WORD:
+ return new PaidCommandParser().parse(arguments);
+
+ case IsPaidCommand.COMMAND_WORD:
+ return new IsPaidCommandParser().parse(arguments);
+
+ case UndoCommand.COMMAND_WORD:
+ return new UndoCommand();
+
+ case RedoCommand.COMMAND_WORD:
+ return new RedoCommand();
+
+ case RevenueCommand.COMMAND_WORD:
+ return new RevenueCommand();
+
+ case UnPaidCommand.COMMAND_WORD:
+ return new UnPaidCommandParser().parse(arguments);
+
+ case UnPaidAllCommand.COMMAND_WORD:
+ return new UnPaidAllCommand();
+
+ case FreeTimeCommand.COMMAND_WORD:
+ return new FreeTimeCommandParser().parse(arguments);
+
default:
logger.finer("This user input caused a ParseException: " + userInput);
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
}
}
-
}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..fe02973ee97 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -10,6 +10,12 @@ public class CliSyntax {
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_SUBJECT = new Prefix("sb/");
+ public static final Prefix PREFIX_DAY = new Prefix("d/");
+ public static final Prefix PREFIX_BEGIN = new Prefix("b/");
+ public static final Prefix PREFIX_END = new Prefix("end/");
+ public static final Prefix PREFIX_PAID = new Prefix("p/");
+ public static final Prefix PREFIX_PAYRATE = new Prefix("pr/");
+ public static final Prefix PREFIX_DURATION = new Prefix("dur/");
}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 46b3309a78b..1a9e7d6950f 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -3,21 +3,20 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Optional;
-import java.util.Set;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.tag.Tag;
+
/**
* Parses input arguments and creates a new EditCommand object
@@ -32,7 +31,8 @@ public class EditCommandParser implements Parser {
public EditCommand parse(String args) throws ParseException {
requireNonNull(args);
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_SUBJECT, PREFIX_DAY, PREFIX_BEGIN, PREFIX_END, PREFIX_PAYRATE);
Index index;
@@ -42,7 +42,8 @@ public EditCommand parse(String args) throws ParseException {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
}
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_SUBJECT, PREFIX_DAY, PREFIX_BEGIN, PREFIX_END, PREFIX_PAYRATE);
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
@@ -58,28 +59,34 @@ public EditCommand parse(String args) throws ParseException {
if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
}
- parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
- if (!editPersonDescriptor.isAnyFieldEdited()) {
- throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
+ if (argMultimap.getValue(PREFIX_SUBJECT).isPresent()) {
+ editPersonDescriptor.setSubject(ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT).get()));
}
- return new EditCommand(index, editPersonDescriptor);
- }
+ if (argMultimap.getValue(PREFIX_DAY).isPresent()) {
+ editPersonDescriptor.setDay(ParserUtil.parseDay(argMultimap.getValue(PREFIX_DAY).get()));
+ editPersonDescriptor.setEditSchedule(true);
+ }
- /**
- * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty.
- * If {@code tags} contain only one element which is an empty string, it will be parsed into a
- * {@code Set} containing zero tags.
- */
- private Optional> parseTagsForEdit(Collection tags) throws ParseException {
- assert tags != null;
+ if (argMultimap.getValue(PREFIX_BEGIN).isPresent()) {
+ editPersonDescriptor.setBegin(ParserUtil.parseBegin(argMultimap.getValue(PREFIX_BEGIN).get()));
+ editPersonDescriptor.setEditSchedule(true);
+ }
- if (tags.isEmpty()) {
- return Optional.empty();
+ if (argMultimap.getValue(PREFIX_END).isPresent()) {
+ editPersonDescriptor.setEnd(ParserUtil.parseEnd(argMultimap.getValue(PREFIX_END).get()));
+ editPersonDescriptor.setEditSchedule(true);
+ }
+
+ if (argMultimap.getValue(PREFIX_PAYRATE).isPresent()) {
+ editPersonDescriptor.setPayRate(ParserUtil.parsePayRate(argMultimap.getValue(PREFIX_PAYRATE).get()));
}
- Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
- return Optional.of(ParserUtil.parseTags(tagSet));
- }
+ if (!editPersonDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditCommand(index, editPersonDescriptor);
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
index 2867bde857b..194dc398e79 100644
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
@@ -1,33 +1,79 @@
package seedu.address.logic.parser;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
-import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import seedu.address.commons.core.LogsCenter;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.SubjectContainsKeywordsPredicate;
+
/**
* Parses input arguments and creates a new FindCommand object
*/
public class FindCommandParser implements Parser {
+ private static final Logger logger = LogsCenter.getLogger(FindCommandParser.class);
+
/**
* 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()) {
+ logger.log(Level.INFO, "Parsing FindCommand with arguments" + args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_SUBJECT);
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_SUBJECT);
+
+ if (!argMultimap.getPreamble().isEmpty() && argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ logger.log(Level.WARNING, "Invalid input format: Both preamble and name specified.");
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
+ if (!argMultimap.getPreamble().isEmpty() && argMultimap.getValue(PREFIX_SUBJECT).isPresent()) {
+ logger.log(Level.WARNING, "No name or subject specified in FindCommand.");
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
- String[] nameKeywords = trimmedArgs.split("\\s+");
+ logger.log(Level.INFO, "Successfully parsed FindCommand.");
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ assert ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()) != null
+ : "Parsed name cannot be null";
+ String nameKeywords = argMultimap.getValue(PREFIX_NAME).get();
+ if (nameKeywords.split("\\s+").length > 1) {
+ throw new ParseException("Name can only take one word.");
+ }
+ ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
+ }
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
- }
+ if (argMultimap.getValue(PREFIX_SUBJECT).isPresent()) {
+ assert ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT).get()) != null
+ : "Parsed subject cannot be null";
+ String subjectKeywords = argMultimap.getValue(PREFIX_SUBJECT).get();
+ if (subjectKeywords.split("\\s+").length > 1) {
+ throw new ParseException("Subject can only take one word.");
+ }
+ ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT).get());
+ }
+
+ List nameKeywords = argMultimap.getAllValues(PREFIX_NAME);
+ List subjectKeywords = argMultimap.getAllValues(PREFIX_SUBJECT);
+ if (nameKeywords.isEmpty() && subjectKeywords.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
+
+ return new FindCommand(
+ new NameContainsKeywordsPredicate(nameKeywords),
+ new SubjectContainsKeywordsPredicate(subjectKeywords));
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/FreeTimeCommandParser.java b/src/main/java/seedu/address/logic/parser/FreeTimeCommandParser.java
new file mode 100644
index 00000000000..9adeb598d4c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/FreeTimeCommandParser.java
@@ -0,0 +1,59 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.FreeTimeCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.interval.Duration;
+import seedu.address.model.interval.Interval;
+import seedu.address.model.interval.IntervalBegin;
+import seedu.address.model.interval.IntervalDay;
+import seedu.address.model.interval.IntervalEnd;
+
+/**
+ * Parses input arguments and creates a new FreeTimeCommand object
+ */
+public class FreeTimeCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FreeTimeCommand
+ * and returns an FreeTimeCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FreeTimeCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_DAY, PREFIX_DURATION, PREFIX_BEGIN, PREFIX_END);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_DAY, PREFIX_DURATION, PREFIX_BEGIN, PREFIX_END)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FreeTimeCommand.MESSAGE_USAGE));
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_DAY, PREFIX_DURATION, PREFIX_BEGIN, PREFIX_END);
+
+ IntervalDay day = ParserUtil.parseIntervalDay(argMultimap.getValue(PREFIX_DAY).get());
+ Duration duration = ParserUtil.parseDuration(argMultimap.getValue(PREFIX_DURATION).get());
+ IntervalBegin begin = ParserUtil.parseIntervalBegin(argMultimap.getValue(PREFIX_BEGIN).get());
+ IntervalEnd end = ParserUtil.parseIntervalEnd(argMultimap.getValue(PREFIX_END).get());
+
+
+ Interval interval = new Interval(day, duration, begin, end);
+
+ return new FreeTimeCommand(interval);
+ }
+
+ /**
+ * 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/IsPaidCommandParser.java b/src/main/java/seedu/address/logic/parser/IsPaidCommandParser.java
new file mode 100644
index 00000000000..349c36040f2
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/IsPaidCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.IsPaidCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new IsPaidCommand object
+ * (Make use of the template of DeleteCommandParser and did some modifications)
+ */
+public class IsPaidCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the IsPaidCommand
+ * and returns a IsPaidCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public IsPaidCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new IsPaidCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, IsPaidCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/ListCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCommandParser.java
new file mode 100644
index 00000000000..8b1a1339344
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java
@@ -0,0 +1,48 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.logic.commands.ListByDayCommand;
+import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ListUnPaidCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.person.Day;
+import seedu.address.model.person.DayPredicate;
+import seedu.address.model.person.PaidPredicate;
+
+/**
+ * Parses input arguments and creates a new ListCommand object
+ */
+public class ListCommandParser implements Parser {
+ private final Logger logger = LogsCenter.getLogger(getClass());
+ /**
+ * 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 ListCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ return new ListCommand();
+ }
+
+ if (trimmedArgs.startsWith("unpaid")) {
+ return new ListUnPaidCommand(new PaidPredicate(true));
+ }
+
+ Day day;
+
+ try {
+ day = new Day(trimmedArgs);
+ } catch (IllegalArgumentException e) {
+ logger.info("[ListCommandParser.parse()]: Invalid extraneous parameter");
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListByDayCommand.MESSAGE_USAGE));
+ }
+
+ return new ListByDayCommand(new DayPredicate(day));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/PaidCommandParser.java b/src/main/java/seedu/address/logic/parser/PaidCommandParser.java
new file mode 100644
index 00000000000..65b574875be
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/PaidCommandParser.java
@@ -0,0 +1,34 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.PaidCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new PaidCommand object
+ * (Make use of the template of DeleteCommandParser and did some modifications)
+ */
+public class PaidCommandParser implements Parser {
+ private final Logger logger = LogsCenter.getLogger(getClass());
+ /**
+ * Parses the given {@code String} of arguments in the context of the PaidCommand
+ * and returns a PaidCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public PaidCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new PaidCommand(index);
+ } catch (ParseException pe) {
+ logger.info("[PaidCommandParser.parse()]: Invalid extraneous parameter");
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, PaidCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..2d7dd41d194 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -2,19 +2,22 @@
import static java.util.Objects.requireNonNull;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.interval.Duration;
+import seedu.address.model.interval.IntervalBegin;
+import seedu.address.model.interval.IntervalDay;
+import seedu.address.model.interval.IntervalEnd;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Begin;
+import seedu.address.model.person.Day;
import seedu.address.model.person.Email;
+import seedu.address.model.person.End;
import seedu.address.model.person.Name;
+import seedu.address.model.person.PayRate;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
+import seedu.address.model.person.Subject;
/**
* Contains utility methods used for parsing strings in the various *Parser classes.
*/
@@ -95,30 +98,149 @@ public static Email parseEmail(String email) throws ParseException {
return new Email(trimmedEmail);
}
+
+ /**
+ * Parses a {@code String day} into an {@code Day}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code day} is invalid.
+ */
+ public static Day parseDay(String day) throws ParseException {
+ requireNonNull(day);
+ String trimmedDay = day.trim();
+ Day initialisedDay;
+
+ try {
+ initialisedDay = new Day(trimmedDay);
+ } catch (IllegalArgumentException e) {
+ throw new ParseException(Day.MESSAGE_CONSTRAINTS);
+ }
+
+ return initialisedDay;
+ }
+
+ /**
+ * Parses a {@code String subject} into an {@code Subject}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code subject} is invalid.
+ */
+ public static Subject parseSubject(String subject) throws ParseException {
+ requireNonNull(subject);
+ String trimmedSubject = subject.trim();
+ if (!Subject.isValidSubject(trimmedSubject)) {
+ throw new ParseException(Subject.MESSAGE_CONSTRAINTS);
+ }
+ return new Subject(trimmedSubject);
+ }
+
+ /**
+ * Parses a {@code String begin} into an {@code Begin}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code begin} is invalid.
+ */
+ public static Begin parseBegin(String begin) throws ParseException {
+ requireNonNull(begin);
+ String trimmedBegin = begin.trim();
+ if (!Begin.isValidBegin(trimmedBegin)) {
+ throw new ParseException(Begin.MESSAGE_CONSTRAINTS);
+ }
+ return new Begin(trimmedBegin);
+ }
+
+ /**
+ * Parses a {@code String end} into an {@code End}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code end} is invalid.
+ */
+ public static End parseEnd(String end) throws ParseException {
+ requireNonNull(end);
+ String trimmedEnd = end.trim();
+ if (!End.isValidEnd(trimmedEnd)) {
+ throw new ParseException(End.MESSAGE_CONSTRAINTS);
+ }
+ return new End(trimmedEnd);
+ }
+
/**
- * Parses a {@code String tag} into a {@code Tag}.
+ * Parses a {@code String payRate} into an {@code PayRate}.
* Leading and trailing whitespaces will be trimmed.
*
- * @throws ParseException if the given {@code tag} is invalid.
+ * @throws ParseException if the given {@code end} 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 PayRate parsePayRate(String payRate) throws ParseException {
+ requireNonNull(payRate);
+ String trimmedPayRate = payRate.trim();
+ if (!PayRate.isValidPayRate(payRate)) {
+ throw new ParseException(PayRate.MESSAGE_CONSTRAINTS);
}
- return new Tag(trimmedTag);
+ return new PayRate(trimmedPayRate);
}
/**
- * Parses {@code Collection tags} into a {@code Set}.
+ * Parses a {@code String day} into an {@code IntervalDay}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code day} is invalid.
+ */
+ public static IntervalDay parseIntervalDay(String day) throws ParseException {
+ requireNonNull(day);
+ String trimmedDay = day.trim();
+ IntervalDay initialisedDay;
+
+ try {
+ initialisedDay = new IntervalDay(trimmedDay);
+ } catch (IllegalArgumentException e) {
+ throw new ParseException(IntervalDay.MESSAGE_CONSTRAINTS);
+ }
+
+ return initialisedDay;
+ }
+
+ /**
+ * Parses a {@code String begin} into an {@code IntervalBegin}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code begin} is invalid.
+ */
+ public static IntervalBegin parseIntervalBegin(String begin) throws ParseException {
+ requireNonNull(begin);
+ String trimmedBegin = begin.trim();
+ if (!IntervalBegin.isValidBegin(trimmedBegin)) {
+ throw new ParseException(IntervalBegin.MESSAGE_CONSTRAINTS);
+ }
+ return new IntervalBegin(trimmedBegin);
+ }
+
+ /**
+ * Parses a {@code String end} into an {@code IntervalEnd}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code end} is invalid.
+ */
+ public static IntervalEnd parseIntervalEnd(String end) throws ParseException {
+ requireNonNull(end);
+ String trimmedEnd = end.trim();
+ if (!IntervalEnd.isValidEnd(trimmedEnd)) {
+ throw new ParseException(IntervalEnd.MESSAGE_CONSTRAINTS);
+ }
+ return new IntervalEnd(trimmedEnd);
+ }
+
+ /**
+ * Parses a {@code String duration} into an {@code Duration}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code end} is invalid.
*/
- public static Set parseTags(Collection tags) throws ParseException {
- requireNonNull(tags);
- final Set tagSet = new HashSet<>();
- for (String tagName : tags) {
- tagSet.add(parseTag(tagName));
+ public static Duration parseDuration(String duration) throws ParseException {
+ requireNonNull(duration);
+ String trimmedDuration = duration.trim();
+ if (!Duration.isValidDuration(trimmedDuration)) {
+ throw new ParseException(Duration.MESSAGE_CONSTRAINTS);
}
- return tagSet;
+ return new Duration(trimmedDuration);
}
}
diff --git a/src/main/java/seedu/address/logic/parser/UnPaidCommandParser.java b/src/main/java/seedu/address/logic/parser/UnPaidCommandParser.java
new file mode 100644
index 00000000000..d73a4a6f2f5
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/UnPaidCommandParser.java
@@ -0,0 +1,33 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.UnPaidCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new UnPaidCommand object
+ * (Make use of the template of DeleteCommandParser and did some modifications)
+ */
+public class UnPaidCommandParser implements Parser {
+ private final Logger logger = LogsCenter.getLogger(getClass());
+ /**
+ * Parses the given {@code String} of arguments in the context of the UnPaidCommand
+ * and returns a UnPaidCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public UnPaidCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new UnPaidCommand(index);
+ } catch (ParseException pe) {
+ logger.info("[UnPaidCommandParser.parse()]: Invalid extraneous parameter");
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnPaidCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 73397161e84..f2098244e03 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -6,6 +6,7 @@
import javafx.collections.ObservableList;
import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.interval.Interval;
import seedu.address.model.person.Person;
import seedu.address.model.person.UniquePersonList;
@@ -67,6 +68,26 @@ public boolean hasPerson(Person person) {
return persons.contains(person);
}
+ /**
+ * Returns true if a person with the same schedule as {@code person} exists in the address book.
+ * @param person
+ * @return boolean for whether a person has a clashing schedule
+ */
+ public boolean hasDate(Person person) {
+ requireNonNull(person);
+ return persons.checkSameDate(person);
+ }
+
+ /**
+ * Finds the list of timings which have the same day as the Interval from the address book
+ * @param interval
+ * @return list of timings
+ */
+ public List findInterval(Interval interval) {
+ requireNonNull(interval);
+ return persons.findInterval(interval);
+ }
+
/**
* Adds a person to the address book.
* The person must not already exist in the address book.
@@ -80,10 +101,34 @@ public void addPerson(Person p) {
* {@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) {
+ public void setPerson(Person target, Person editedPerson, boolean isEditingSchedule) {
requireNonNull(editedPerson);
- persons.setPerson(target, editedPerson);
+ persons.setPerson(target, editedPerson, isEditingSchedule);
+ }
+
+ /**
+ * Set the person as paid in the address book.
+ * The person must exist in the address book.
+ */
+ public void setPaid(Person target) {
+ persons.setPaid(target);
+ }
+
+ /**
+ * Set the person as not paid in the address book.
+ * The person must exist in the address book.
+ */
+ public void setUnPaid(Person target) {
+ persons.setUnPaid(target);
+ }
+
+ /**
+ * Get the person's paid status.
+ * The person must exist in the address book.
+ */
+ public boolean getPaid(Person target) {
+ return persons.getPaid(target);
}
/**
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..91ea6869383 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -1,10 +1,12 @@
package seedu.address.model;
import java.nio.file.Path;
+import java.util.List;
import java.util.function.Predicate;
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.interval.Interval;
import seedu.address.model.person.Person;
/**
@@ -14,6 +16,8 @@ public interface Model {
/** {@code Predicate} that always evaluate to true */
Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_UNPAID_PERSONS = paid -> false;
+
/**
* Replaces user prefs data with the data in {@code userPrefs}.
*/
@@ -57,12 +61,22 @@ public interface Model {
*/
boolean hasPerson(Person person);
+ boolean hasDate(Person person);
+ List findInterval(Interval interval);
+
/**
* Deletes the given person.
* The person must exist in the address book.
*/
void deletePerson(Person target);
+ void markPersonPaid(Person target);
+
+ void markPersonUnPaid(Person target);
+
+ boolean getPersonPaid(Person target);
+
+
/**
* Adds the given person.
* {@code person} must not already exist in the address book.
@@ -74,14 +88,52 @@ public interface Model {
* {@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.
*/
- void setPerson(Person target, Person editedPerson);
+ void setPerson(Person target, Person editedPerson, boolean isEditingSchedule);
/** Returns an unmodifiable view of the filtered person list */
ObservableList getFilteredPersonList();
+ /** Returns an unmodifiable view of the schedule list */
+ ObservableList getScheduleList();
+
+ /** Returns an unmodifiable view of the unfiltered person list */
+ ObservableList getUnfilteredPersonList();
+
/**
* Updates the filter of the filtered person list to filter by the given {@code predicate}.
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ /**
+ * Saves the current addressbook {@code VersionedAddressBook} state in its history
+ */
+ void commitAddressBook();
+
+ /**
+ * Restore the previous addressbook {@code VersionedAddressBook} state from its history
+ */
+ void undoAddressBook();
+
+ /**
+ * Restore a previously undone addressbook {@code VersionedAddressBook} state from its history
+ */
+ void redoAddressBook();
+
+ /**
+ * Remove states in the {@code VersionedAddressBook} that are no longer valid
+ */
+ void purgeAddressBook();
+
+ /**
+ * Checks whether the addressbook has any saved states that can be restored
+ * @return a boolean to indicate whether an undo operation is possible
+ */
+ boolean canUndoAddressBook();
+
+ /**
+ * Checks whether the addressbook has any saved undone states that can be restored
+ * @return a boolean to indicate whether an undo operation is possible
+ */
+ boolean canRedoAddressBook();
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 57bc563fde6..8914c083377 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -4,13 +4,17 @@
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import java.nio.file.Path;
+import java.util.List;
import java.util.function.Predicate;
import java.util.logging.Logger;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
+import javafx.collections.transformation.SortedList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.interval.Interval;
+import seedu.address.model.person.LessonComparator;
import seedu.address.model.person.Person;
/**
@@ -18,10 +22,12 @@
*/
public class ModelManager implements Model {
private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
-
- private final AddressBook addressBook;
+ private final VersionedAddressBook addressBook;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
+ private final SortedList scheduleList;
+
+ private final ObservableList unfilteredPersons;
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
@@ -31,13 +37,15 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs
logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
- this.addressBook = new AddressBook(addressBook);
+ this.addressBook = new VersionedAddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
+ unfilteredPersons = new FilteredList<>(this.addressBook.getPersonList());
filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ scheduleList = new SortedList<>(this.addressBook.getPersonList(), new LessonComparator());
}
public ModelManager() {
- this(new AddressBook(), new UserPrefs());
+ this(new VersionedAddressBook(), new UserPrefs());
}
//=========== UserPrefs ==================================================================================
@@ -93,11 +101,38 @@ public boolean hasPerson(Person person) {
return addressBook.hasPerson(person);
}
+ @Override
+ public boolean hasDate(Person person) {
+ requireNonNull(person);
+ return addressBook.hasDate(person);
+ }
+
+ @Override
+ public List findInterval(Interval interval) {
+ requireNonNull(interval);
+ return addressBook.findInterval(interval);
+ }
+
@Override
public void deletePerson(Person target) {
addressBook.removePerson(target);
}
+ @Override
+ public void markPersonPaid(Person target) {
+ addressBook.setPaid(target);
+ }
+
+ @Override
+ public void markPersonUnPaid(Person target) {
+ addressBook.setUnPaid(target);
+ }
+
+ @Override
+ public boolean getPersonPaid(Person target) {
+ return addressBook.getPaid(target);
+ }
+
@Override
public void addPerson(Person person) {
addressBook.addPerson(person);
@@ -105,12 +140,45 @@ public void addPerson(Person person) {
}
@Override
- public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
+ public void setPerson(Person target, Person editedPerson, boolean isEditingSchedule) {
+ requireAllNonNull(target, editedPerson, isEditingSchedule);
+
+ addressBook.setPerson(target, editedPerson, isEditingSchedule);
+ }
+
+ @Override
+ public void commitAddressBook() {
+ addressBook.commit();
+ }
+
+ @Override
+ public void undoAddressBook() {
+ addressBook.undo();
+ }
+
+ @Override
+ public void redoAddressBook() {
+ addressBook.redo();
+ }
+
+ @Override
+ public boolean canUndoAddressBook() {
+ return addressBook.canUndo();
+ }
+
+ @Override
+ public boolean canRedoAddressBook() {
+ return addressBook.canRedo();
+ }
- addressBook.setPerson(target, editedPerson);
+ @Override
+ public void purgeAddressBook() {
+ addressBook.purge();
}
+
+
+
//=========== Filtered Person List Accessors =============================================================
/**
@@ -128,6 +196,28 @@ public void updateFilteredPersonList(Predicate predicate) {
filteredPersons.setPredicate(predicate);
}
+ //=========== Schedule List Accessors =============================================================
+
+ /**
+ * Returns an unmodifiable view of the list of {@code Person} sorted by date backed by the internal list of
+ * {@code versionedAddressBook}
+ */
+ @Override
+ public ObservableList getScheduleList() {
+ return scheduleList;
+ }
+
+ //=========== Full List Accessors ==================================================================
+
+ /**
+ * Returns an unmodifiable unfiltered view of the list of {@code Person} backed by the internal list of
+ * {@code versionedAddressBook}
+ */
+ @Override
+ public ObservableList getUnfilteredPersonList() {
+ return unfilteredPersons;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -144,5 +234,4 @@ public boolean equals(Object other) {
&& userPrefs.equals(otherModelManager.userPrefs)
&& filteredPersons.equals(otherModelManager.filteredPersons);
}
-
}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 6be655fb4c7..13456ba8615 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -14,7 +14,7 @@
public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
- private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path addressBookFilePath = Paths.get("data" , "tuitionconnect.json");
/**
* Creates a {@code UserPrefs} with default values.
diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedAddressBook.java
new file mode 100644
index 00000000000..622f93b1b9c
--- /dev/null
+++ b/src/main/java/seedu/address/model/VersionedAddressBook.java
@@ -0,0 +1,106 @@
+package seedu.address.model;
+
+import java.util.ArrayList;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+
+/**
+ * Represents an AddressBook with an undo/redo history
+ */
+public class VersionedAddressBook extends AddressBook {
+
+ private final ArrayList addressBookStateList;
+ private int currentStatePointer;
+ private final Logger logger = LogsCenter.getLogger(getClass());
+
+ /**
+ * Creates a Versioned AddressBook
+ */
+ public VersionedAddressBook() {
+ super();
+ addressBookStateList = new ArrayList<>();
+
+ // Save a copy of initial state into state list
+ AddressBook initState = new AddressBook(this);
+ addressBookStateList.add(initState);
+
+ // Initialise pointer to start of list
+ currentStatePointer = 0;
+
+ logger.info("Versioned Address Book is initialised");
+ }
+
+ /**
+ * Creates a Versioned AddressBook using the Persons in the {@code toBeCopied}
+ */
+ public VersionedAddressBook(ReadOnlyAddressBook toBeCopied) {
+ super(toBeCopied);
+ addressBookStateList = new ArrayList<>();
+
+ // Save a copy of initial state into state list
+ AddressBook initState = new AddressBook(this);
+ addressBookStateList.add(initState);
+
+ // Initialise pointer to start of list
+ currentStatePointer = 0;
+ }
+
+ /**
+ * Gets {@code AddressBook} that the pointer is pointing to
+ * @return current address book
+ */
+ private AddressBook getCurrentAddressBook() {
+ return addressBookStateList.get(currentStatePointer);
+ }
+
+ // Commits a copy of current address book so that the saved state is immutable
+
+ /**
+ * Commits a defensive copy of current {@code AddressBook} to {@code addressBookStateList}
+ * so that the saved state is immutable
+ */
+ public void commit() {
+ // Save a copy of the state into the state list
+ AddressBook copy = new AddressBook(this);
+ addressBookStateList.add(copy);
+
+ currentStatePointer++;
+
+ logger.info("[VersionedAddressBook.commit()]: Versioned Address Book commit to memory");
+ }
+
+ /**
+ * Shifts {@code currentStatePointer} to the left to restore previous version of the addressbook
+ */
+ public void undo() {
+ currentStatePointer--;
+ this.resetData(getCurrentAddressBook());
+ }
+
+ /**
+ * Shifts {@code currentStatePointer} to the right to restore undoned version of the addressbook
+ */
+ public void redo() {
+ currentStatePointer++;
+ this.resetData(getCurrentAddressBook());
+ }
+
+ public boolean canUndo() {
+ return currentStatePointer > 0;
+ }
+
+ public boolean canRedo() {
+ return addressBookStateList.size() > 1 && currentStatePointer < addressBookStateList.size() - 1;
+ }
+
+ /**
+ * Remove redundant {@code AddressBook} states from addressBookStateList
+ */
+ public void purge() {
+ // Remove version data after the current state
+ if (addressBookStateList.size() > currentStatePointer + 1) {
+ addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear();
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/interval/Duration.java b/src/main/java/seedu/address/model/interval/Duration.java
new file mode 100644
index 00000000000..f2d29c18a77
--- /dev/null
+++ b/src/main/java/seedu/address/model/interval/Duration.java
@@ -0,0 +1,73 @@
+package seedu.address.model.interval;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a duration in an interval
+ */
+
+public class Duration {
+ public static final String MESSAGE_CONSTRAINTS = "The duration must be a positive integer";
+
+ public final String value;
+
+
+ /**
+ * Constructs a {@code Begin}.
+ *
+ * @param duration A valid duration
+ */
+ public Duration(String duration) {
+ requireNonNull(duration);
+ checkArgument(isValidDuration(duration), MESSAGE_CONSTRAINTS);
+ value = duration;
+ }
+
+ /**
+ * Checks if it is a valid duration
+ * @param test String to be checked
+ * @return boolean on whether the value is a valid duration
+ */
+ public static boolean isValidDuration(String test) {
+ try {
+ int number = Integer.parseInt(test);
+ return number > 0;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ /**
+ * @return defensive copy of Duration
+ */
+
+ public Duration copy() {
+ return new Duration(this.value);
+ }
+
+ public int toInt() {
+ return Integer.parseInt(value);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Duration)) {
+ return false;
+ }
+
+ Duration otherDuration = (Duration) other;
+ return value.equals(otherDuration.value);
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/interval/Interval.java b/src/main/java/seedu/address/model/interval/Interval.java
new file mode 100644
index 00000000000..5fe5749ed84
--- /dev/null
+++ b/src/main/java/seedu/address/model/interval/Interval.java
@@ -0,0 +1,84 @@
+package seedu.address.model.interval;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Objects;
+
+import seedu.address.commons.util.ToStringBuilder;
+
+
+/**
+ * Represents an Interval in the address book.
+ */
+public class Interval {
+
+ private final IntervalDay intervalDay;
+ private final IntervalBegin intervalBegin;
+ private final IntervalEnd intervalEnd;
+ private final Duration duration;
+
+ /**
+ * Constructor for interval class
+ * @param intervalDay
+ * @param duration
+ * @param intervalBegin
+ * @param intervalEnd
+ */
+ public Interval(IntervalDay intervalDay, Duration duration, IntervalBegin intervalBegin, IntervalEnd intervalEnd) {
+ requireAllNonNull(intervalDay, intervalBegin, intervalDay, duration);
+ this.intervalDay = intervalDay;
+ this.duration = duration;
+ this.intervalBegin = intervalBegin;
+ this.intervalEnd = intervalEnd;
+ }
+
+ public IntervalDay getIntervalDay() {
+ return intervalDay.copy();
+ }
+
+ public Duration getDuration() {
+ return duration.copy();
+ }
+
+ public IntervalBegin getIntervalBegin() {
+ return intervalBegin.copy();
+ }
+
+ public IntervalEnd getIntervalEnd() {
+ return intervalEnd.copy();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Interval)) {
+ return false;
+ }
+
+ Interval otherInterval = (Interval) other;
+ return intervalDay.equals(otherInterval.intervalDay)
+ && duration.equals(otherInterval.duration)
+ && intervalBegin.equals(otherInterval.intervalBegin)
+ && intervalEnd.equals(otherInterval.intervalEnd);
+ }
+
+ @Override
+ public int hashCode() {
+ // use this method for custom fields hashing instead of implementing your own
+ return Objects.hash(intervalDay, duration, intervalBegin, intervalEnd);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("intervalDay", intervalDay)
+ .add("duration", duration)
+ .add("intervalBegin", intervalBegin)
+ .add("intervalEnd", intervalEnd)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/interval/IntervalBegin.java b/src/main/java/seedu/address/model/interval/IntervalBegin.java
new file mode 100644
index 00000000000..b9ac3dc0abd
--- /dev/null
+++ b/src/main/java/seedu/address/model/interval/IntervalBegin.java
@@ -0,0 +1,52 @@
+package seedu.address.model.interval;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import seedu.address.model.person.Begin;
+
+/**
+ * Represents a Interval's Begin in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidBegin(String)}
+ */
+
+public class IntervalBegin extends Begin {
+
+ /**
+ * Constructs a {@code Begin}.
+ *
+ * @param begin A valid phone number.
+ */
+ public IntervalBegin(String begin) {
+ super(begin);
+ }
+
+ public Date getTimes() throws ParseException {
+ assert isValidBegin(value);
+ SimpleDateFormat dateFormat = new SimpleDateFormat("HHmm");
+ return dateFormat.parse(value);
+ }
+
+ /**
+ * @return a defensive copy of IntervalBegin
+ */
+ public IntervalBegin copy() {
+ return new IntervalBegin(this.value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof seedu.address.model.interval.IntervalBegin)) {
+ return false;
+ }
+
+ seedu.address.model.interval.IntervalBegin otherBegin = (seedu.address.model.interval.IntervalBegin) other;
+ return value.equals(otherBegin.value);
+ }
+}
diff --git a/src/main/java/seedu/address/model/interval/IntervalDay.java b/src/main/java/seedu/address/model/interval/IntervalDay.java
new file mode 100644
index 00000000000..5f0527cd097
--- /dev/null
+++ b/src/main/java/seedu/address/model/interval/IntervalDay.java
@@ -0,0 +1,80 @@
+package seedu.address.model.interval;
+
+import seedu.address.model.person.Day;
+
+/**
+ * Represents the day in the interval
+ * Guarantees: immutable; is valid as declared in {@link #isValidDay(String)}
+ */
+public class IntervalDay extends Day {
+
+ /**
+ * Constructs a {@code Day}.
+ *
+ * @param day A valid day.
+ */
+ public IntervalDay(String day) {
+ super(day);
+ }
+
+ /**
+ * Parses the day input
+ * @param input string of Day
+ * @return parses the day into the complete day name
+ */
+ public String parseDay(String input) {
+ String day = input.toLowerCase();
+ String result = "";
+ switch (day) {
+ case "mon":
+ case "monday":
+ result = "Mon";
+ break;
+ case "tue":
+ case "tuesday":
+ result = "Tue";
+ break;
+ case "wed":
+ case "wednesday":
+ result = "Wed";
+ break;
+ case "thu":
+ case "thursday":
+ result = "Thu";
+ break;
+ case "fri":
+ case "friday":
+ result = "Fri";
+ break;
+ case "sat":
+ case "saturday":
+ result = "Sat";
+ break;
+ case "sun":
+ case "sunday":
+ result = "Sun";
+ break;
+ default:
+ throw new IllegalArgumentException(MESSAGE_CONSTRAINTS);
+ }
+ return result;
+ }
+
+ public static boolean isValidDay(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ /**
+ * @return a defensive copy of IntervalDay
+ */
+ public IntervalDay copy() {
+ return new IntervalDay(this.stringValue);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof seedu.address.model.interval.IntervalDay // instanceof handles nulls
+ && value.equals(((seedu.address.model.interval.IntervalDay) other).value)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/interval/IntervalEnd.java b/src/main/java/seedu/address/model/interval/IntervalEnd.java
new file mode 100644
index 00000000000..f91becb0e83
--- /dev/null
+++ b/src/main/java/seedu/address/model/interval/IntervalEnd.java
@@ -0,0 +1,53 @@
+package seedu.address.model.interval;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import seedu.address.model.person.End;
+
+/**
+ * Represents an Interval's End in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidEnd(String)}
+ */
+
+public class IntervalEnd extends End {
+
+ /**
+ * Constructs a {@code End}.
+ *
+ * @param end A valid phone number.
+ */
+ public IntervalEnd(String end) {
+ super(end);
+ }
+
+ public Date getTimes() throws ParseException {
+ assert isValidEnd(value);
+ SimpleDateFormat dateFormat = new SimpleDateFormat("HHmm");
+ return dateFormat.parse(value);
+ }
+
+ /**
+ * @return defensive copy of IntervalEnd
+ */
+ public IntervalEnd copy() {
+ return new IntervalEnd(this.value);
+ }
+
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof seedu.address.model.interval.IntervalEnd)) {
+ return false;
+ }
+
+ seedu.address.model.interval.IntervalEnd otherEnd = (seedu.address.model.interval.IntervalEnd) other;
+ return value.equals(otherEnd.value);
+ }
+}
diff --git a/src/main/java/seedu/address/model/interval/TimeSlot.java b/src/main/java/seedu/address/model/interval/TimeSlot.java
new file mode 100644
index 00000000000..43511272620
--- /dev/null
+++ b/src/main/java/seedu/address/model/interval/TimeSlot.java
@@ -0,0 +1,149 @@
+package seedu.address.model.interval;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import seedu.address.commons.core.LogsCenter;
+
+/**
+ * TimeSlot Class
+ */
+public class TimeSlot {
+ private static final Logger logger = LogsCenter.getLogger(TimeSlot.class);
+ private Date start;
+ private Date end;
+
+ /**
+ * Constructor for TimeSlot
+ * @param start a Date object for start
+ * @param end a Date object for end
+ */
+ public TimeSlot(Date start, Date end) {
+ requireAllNonNull(start, end);
+ this.start = start;
+ this.end = end;
+ }
+
+ /**
+ * @return the duration of the timeslot in minutes
+ */
+ public long getDurationMinutes() {
+ return (end.getTime() - start.getTime()) / (60 * 1000);
+ }
+
+ /**
+ * Parses a list of String into a list of TimeSlots.
+ * @param timeSlots the list of strings to be parsed
+ * @return parsed timeslots
+ * @throws ParseException
+ */
+ public static List parseIntervals(List timeSlots) throws ParseException {
+ List timeSlotObjects = new ArrayList<>();
+ SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm");
+ Pattern timePattern = Pattern.compile("\\d{2}:\\d{2}");
+ for (String timeSlot : timeSlots) {
+ assert timeSlot.charAt(6) == '-';
+ String[] times = timeSlot.split(" - ");
+ assert timePattern.matcher(times[0]).matches();
+ assert timePattern.matcher(times[1]).matches();
+ Date start = dateFormat.parse(times[0]);
+ Date end = dateFormat.parse(times[1]);
+ timeSlotObjects.add(new TimeSlot(start, end));
+ }
+ return timeSlotObjects;
+ }
+
+ /**
+ * Finds the available time within the given list of timeslots and interval
+ * @param timeslots
+ * @param interval
+ * @return a list of available timeslots
+ * @throws ParseException
+ */
+ public static List findAvailableTime(List timeslots, Interval interval) throws ParseException {
+ timeslots.sort(Comparator.comparing(timeslot -> timeslot.getStart()));
+
+ List availableTime = new ArrayList<>();
+ Date lastEnd = interval.getIntervalBegin().getTimes();
+ for (TimeSlot timeslot : timeslots) {
+ if (timeslot.getStart().after(interval.getIntervalEnd().getTimes())) {
+ availableTime.add(new TimeSlot(lastEnd, interval.getIntervalEnd().getTimes()));
+ } else if (timeslot.getStart().after(lastEnd)) {
+ availableTime.add(new TimeSlot(lastEnd, timeslot.getStart()));
+ }
+ if (lastEnd.before(timeslot.getEnd())) {
+ lastEnd = timeslot.getEnd();
+ }
+ }
+ if (lastEnd.before(interval.getIntervalEnd().getTimes())) {
+ availableTime.add(new TimeSlot(lastEnd, interval.getIntervalEnd().getTimes()));
+ }
+ List validTimeSlots = availableTime.stream()
+ .filter(timeslot -> timeslot.getDurationMinutes() >= interval.getDuration().toInt())
+ .collect(Collectors.toList());
+
+ return validTimeSlots;
+ }
+
+ /**
+ * Prints out the results
+ * @param timeslots
+ * @return String of results
+ */
+ public static String printResults(List timeslots) {
+ if (timeslots.size() == 0) {
+ logger.info("[TimeSlot.printResults]: No timeslots are found");
+ return "There are no available timeslots.";
+ }
+
+ String result = "";
+
+ for (TimeSlot timeslot : timeslots) {
+ logger.info("[TimeSlot.printResults]: Timeslots found");
+ SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm");
+ String startTime = timeFormat.format(timeslot.getStart());
+ String endTime = timeFormat.format(timeslot.getEnd());
+ result = result + "Free from " + startTime + " - " + endTime + "\n";
+ }
+ return result;
+ }
+
+ /**
+ * @return start
+ */
+ public Date getStart() {
+ return start;
+ }
+
+ /**
+ * @return end
+ */
+ public Date getEnd() {
+ return end;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof TimeSlot)) {
+ return false;
+ }
+
+ TimeSlot otherTimeslot = (TimeSlot) other;
+ return start.equals(otherTimeslot.getStart())
+ && end.equals(otherTimeslot.getEnd());
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java
index 469a2cc9a1e..df970ddad07 100644
--- a/src/main/java/seedu/address/model/person/Address.java
+++ b/src/main/java/seedu/address/model/person/Address.java
@@ -37,6 +37,14 @@ public static boolean isValidAddress(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * @return a defensive copy of address
+ */
+
+ public Address copy() {
+ return new Address(this.value);
+ }
+
@Override
public String toString() {
return value;
diff --git a/src/main/java/seedu/address/model/person/Begin.java b/src/main/java/seedu/address/model/person/Begin.java
new file mode 100644
index 00000000000..5ae2c949ffa
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Begin.java
@@ -0,0 +1,67 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Represents a Person's Begin in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidBegin(String)}
+ */
+
+public class Begin {
+
+ public static final String MESSAGE_CONSTRAINTS = "Begin has a format of HHMM";
+ public static final String VALIDATION_REGEX = "^(0[0-9]|1[0-9]|2[0-3])[0-5][0-9]$";
+ public final String value;
+
+ /**
+ * Constructs a {@code Begin}.
+ *
+ * @param begin A valid phone number.
+ */
+ public Begin(String begin) {
+ requireNonNull(begin);
+ checkArgument(isValidBegin(begin), MESSAGE_CONSTRAINTS);
+ value = begin;
+ }
+
+ public static boolean isValidBegin(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ private LocalTime parse(String test) {
+ assert isValidBegin(test);
+
+ String pattern = "HHmm";
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
+
+ return LocalTime.parse(test, formatter);
+ }
+
+ public LocalTime getTime() {
+ return parse(value);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Begin)) {
+ return false;
+ }
+
+ Begin otherBegin = (Begin) other;
+ return value.equals(otherBegin.value);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Day.java b/src/main/java/seedu/address/model/person/Day.java
new file mode 100644
index 00000000000..8f46b26f27d
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Day.java
@@ -0,0 +1,84 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.DayOfWeek;
+import java.time.format.TextStyle;
+import java.util.Locale;
+
+/**
+ * Represents the day when the Tutee has tuition every week.
+ * Guarantees: immutable; is valid as declared in {@link #isValidDay(String)}
+ */
+public class Day {
+ public static final String MESSAGE_CONSTRAINTS =
+ "Days should be written using their full names or their first three letters, and it should not be blank";
+
+ public static final String VALIDATION_REGEX =
+ "(?i)^(Mon|Monday|Tue|Tuesday|Wed|Wednesday|Thu|Thursday|Fri|Friday|Sat|Saturday|Sun|Sunday)$";
+
+ public final DayOfWeek value;
+ public final String stringValue;
+
+ /**
+ * Constructs a {@code Day}.
+ *
+ * @param day A valid day.
+ */
+ public Day(String day) {
+ requireNonNull(day);
+ value = parse(day);
+ stringValue = value.getDisplayName(TextStyle.SHORT, Locale.ENGLISH);
+ }
+
+ /**
+ * Parses an input as a DayOfWeek object.
+ *
+ * @param test Command input
+ * @return DayOfWeek object
+ */
+ public static DayOfWeek parse(String test) {
+ String lowerCaseTest = test.toLowerCase();
+ switch (lowerCaseTest) {
+ case "mon":
+ case "monday":
+ return DayOfWeek.MONDAY;
+ case "tue":
+ case "tuesday":
+ return DayOfWeek.TUESDAY;
+ case "wed":
+ case "wednesday":
+ return DayOfWeek.WEDNESDAY;
+ case "thu":
+ case "thursday":
+ return DayOfWeek.THURSDAY;
+ case "fri":
+ case "friday":
+ return DayOfWeek.FRIDAY;
+ case "sat":
+ case "saturday":
+ return DayOfWeek.SATURDAY;
+ case "sun":
+ case "sunday":
+ return DayOfWeek.SUNDAY;
+ default:
+ throw new IllegalArgumentException(MESSAGE_CONSTRAINTS);
+ }
+ }
+
+ public static boolean isValidDay(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public String toString() {
+ return stringValue;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Day // instanceof handles nulls
+ && value.equals(((Day) other).value)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/DayPredicate.java b/src/main/java/seedu/address/model/person/DayPredicate.java
new file mode 100644
index 00000000000..72f50b049b8
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/DayPredicate.java
@@ -0,0 +1,44 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.ToStringBuilder;
+
+/**
+ * Tests that a {@code Person}'s {@code Day} matches the day given.
+ */
+public class DayPredicate implements Predicate {
+ private final Day day;
+
+ public DayPredicate(Day day) {
+ this.day = day;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ requireNonNull(person);
+ return person.getDay().equals(this.day);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DayPredicate)) {
+ return false;
+ }
+
+ DayPredicate otherDayPredicate = (DayPredicate) other;
+ return day.equals(otherDayPredicate.day);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("day", day).toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
index c62e512bc29..4dfbcb8d062 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/person/Email.java
@@ -51,6 +51,13 @@ public static boolean isValidEmail(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * @return a defensive copy of email.
+ */
+ public Email copy() {
+ return new Email(this.value);
+ }
+
@Override
public String toString() {
return value;
diff --git a/src/main/java/seedu/address/model/person/End.java b/src/main/java/seedu/address/model/person/End.java
new file mode 100644
index 00000000000..043cbf08581
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/End.java
@@ -0,0 +1,67 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Represents a Person's End in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidEnd(String)}
+ */
+
+public class End {
+
+ public static final String MESSAGE_CONSTRAINTS = "That is not a valid time format. End has a format of HHMM";
+ public static final String VALIDATION_REGEX = "^(0[0-9]|1[0-9]|2[0-3])[0-5][0-9]$";
+ public final String value;
+
+ /**
+ * Constructs a {@code End}.
+ *
+ * @param end A valid phone number.
+ */
+ public End(String end) {
+ requireNonNull(end);
+ checkArgument(isValidEnd(end), MESSAGE_CONSTRAINTS);
+ value = end;
+ }
+
+ public static boolean isValidEnd(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ private LocalTime parse(String test) {
+ assert isValidEnd(test);
+
+ String pattern = "HHmm";
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
+
+ return LocalTime.parse(test, formatter);
+ }
+
+ public LocalTime getTime() {
+ return parse(value);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof End)) {
+ return false;
+ }
+
+ End otherEnd = (End) other;
+ return value.equals(otherEnd.value);
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Lesson.java b/src/main/java/seedu/address/model/person/Lesson.java
new file mode 100644
index 00000000000..89d4f075183
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Lesson.java
@@ -0,0 +1,150 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.DayOfWeek;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.temporal.TemporalAdjusters;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+
+/**
+ * Represents the day and time when the Tutee has tuition every week.
+ * Guarantees: immutable; is valid as declared in
+ */
+public class Lesson {
+ public final DayOfWeek day;
+
+ public final LocalTime begin;
+
+ public final LocalTime end;
+
+ private final Logger logger = LogsCenter.getLogger(getClass());
+ /**
+ * Constructs a {@code DayTime}.
+ *
+ * @param day A valid day.
+ * @param begin A valid begin.
+ * @param end A valid end.
+ */
+ public Lesson(Day day, Begin begin, End end) {
+ requireNonNull(day);
+ requireNonNull(begin);
+ requireNonNull(end);
+
+ assert(begin.getTime().isBefore(end.getTime())) : "begin time must be before end time";
+
+ this.day = day.value;
+ this.begin = begin.getTime();
+ this.end = end.getTime();
+ }
+
+ /**
+ * Constructor for defensive coding
+ * @param original
+ */
+ public Lesson(Lesson original) {
+ requireNonNull(original);
+ this.day = original.day;
+ this.begin = original.begin;
+ this.end = original.end;
+ }
+
+ public static boolean isValid(Begin begin, End end) {
+ return begin.getTime().compareTo(end.getTime()) < 0;
+ }
+
+ public String getTimeSlot() {
+ return begin.toString() + " - " + end.toString();
+ }
+
+ public Lesson copy() {
+ return new Lesson(this);
+ }
+
+ @Override
+ public String toString() {
+ return day.toString() + ", " + begin.toString() + " - " + end.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Lesson)) {
+ return false;
+ }
+
+ Lesson otherLesson = (Lesson) other;
+ return day.equals(otherLesson.day)
+ && begin.equals(otherLesson.begin)
+ && end.equals(otherLesson.end);
+ }
+
+ /**
+ * Calculates the number of occurrences of a specific day of the week
+ * within a given month and year.
+ *
+ * @param year The year (4 digits) for which to calculate the occurrences.
+ * @param month The month (1-12) for which to calculate the occurrences.
+ * @return The number of occurrences of the specified day in the month.
+ */
+ public int getNumOfDaysInMonth(int year, int month) {
+ requireNonNull(day);
+ assert (1000 <= year) && (year <= 9999) : "Year must be exactly 4 digits";
+ assert (1 <= month) && (month <= 12) : "Month must be from January to December";
+
+ LocalDate firstDayOfMonth = LocalDate.of(year, month, 1);
+ LocalDate lastDayOfMonth = firstDayOfMonth.with(TemporalAdjusters.lastDayOfMonth());
+ LocalDate temp = firstDayOfMonth;
+
+ int count = 0;
+ while (!temp.isAfter(lastDayOfMonth)) {
+ if (temp.getDayOfWeek() == day) {
+ count++;
+ temp = temp.plusDays(7);
+ } else {
+ temp = temp.plusDays(1);
+ }
+ }
+ assert count > 0 : "number of days should be more than 0";
+ return count;
+ }
+
+ /**
+ * Calculates the lesson duration in hours.
+ *
+ * @return duration in terms of hours.
+ */
+ public double calculateLessonDuration() {
+ try {
+ Duration duration = Duration.between(begin, end);
+ long minutes = duration.toMinutes();
+ double hours = (double) minutes / 60; // Convert minutes to a fraction of hours
+ assert hours > 0.0 : "hours should be positive";
+ return hours;
+ } catch (ArithmeticException e) {
+ logger.info("[Lesson.calculateLessonDuration()]: Duration capacity exceeded");
+ return 1.0;
+ }
+ }
+
+ /**
+ * Calculates the monthly lesson duration in hours.
+ *
+ * @return monthly duration in terms of hours.
+ */
+ public double getMonthlyHours() {
+ double hours = calculateLessonDuration() * getNumOfDaysInMonth(LocalDate.now()
+ .getYear(), LocalDate.now().getMonthValue());
+ assert hours > 0.0 : "hours should be positive";
+ return hours;
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/LessonComparator.java b/src/main/java/seedu/address/model/person/LessonComparator.java
new file mode 100644
index 00000000000..2d9db993f3b
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/LessonComparator.java
@@ -0,0 +1,50 @@
+package seedu.address.model.person;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.util.Comparator;
+
+
+/**
+ * Compares a {@code Person}'s {@code Lesson} against another {@code Lesson}.
+ */
+public class LessonComparator implements Comparator {
+ /**
+ * Compares two Person objects based on the lesson they are currently attending.
+ *
+ * @param person1 the first Person object to be compared
+ * @param person2 the second Person object to be compared
+ * @return a negative integer, zero, or a positive integer as the first
+ * Person's lesson is less than, equal to, or greater than the second Person's lesson.
+ */
+ public int compare(Person person1, Person person2) {
+ Lesson lesson1 = person1.getLesson();
+ Lesson lesson2 = person2.getLesson();
+
+ DayOfWeek currentDay = LocalDate.now().getDayOfWeek();
+
+ // Compare the days cyclically
+ // if lesson1 is earlier in the week than lesson 2, dayComparison return negative
+ int dayComparison = compareDaysCyclically(lesson1.day, currentDay)
+ - compareDaysCyclically(lesson2.day, currentDay);
+
+ if (dayComparison != 0) {
+ return dayComparison;
+ }
+ // If days are the same, compare by start time
+ return lesson1.begin.compareTo(lesson2.begin);
+ }
+
+
+ /**
+ * Returns the days from day 2 to day 1 in a cyclical manner
+ *
+ * @param day1
+ * @param day2
+ * @return days from day2 to day1
+ */
+ public int compareDaysCyclically(DayOfWeek day1, DayOfWeek day2) {
+ // returns days required to get from day 2 to day 1 in a cyclical manner
+ return (day1.getValue() - day2.getValue() + DayOfWeek.values().length) % DayOfWeek.values().length;
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
index 173f15b9b00..12a2296fb50 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/Name.java
@@ -38,6 +38,13 @@ public static boolean isValidName(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * @return a defensive copy of Name
+ */
+ public Name copy() {
+ return new Name(this.fullName);
+ }
+
@Override
public String toString() {
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
index 62d19be2977..b43da2359e9 100644
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
@@ -18,7 +18,7 @@ public NameContainsKeywordsPredicate(List keywords) {
@Override
public boolean test(Person person) {
- return keywords.stream()
+ return keywords.isEmpty() || keywords.stream()
.anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
}
diff --git a/src/main/java/seedu/address/model/person/NameSubjectPredicate.java b/src/main/java/seedu/address/model/person/NameSubjectPredicate.java
new file mode 100644
index 00000000000..ebb0c8c3e8e
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/NameSubjectPredicate.java
@@ -0,0 +1,48 @@
+package seedu.address.model.person;
+
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.ToStringBuilder;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+public class NameSubjectPredicate implements Predicate {
+ private final NameContainsKeywordsPredicate name;
+ private final SubjectContainsKeywordsPredicate subject;
+
+ /**
+ * Constructor for the NameSubjectPredicate class
+ * @param name the keyword starting with the prefix n/
+ * @param subject the keyword starting with the prefix sb/
+ */
+ public NameSubjectPredicate(NameContainsKeywordsPredicate name, SubjectContainsKeywordsPredicate subject) {
+ this.name = name;
+ this.subject = subject;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return name.test(person) && subject.test(person);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof NameSubjectPredicate)) {
+ return false;
+ }
+
+ NameSubjectPredicate otherNameSubjectPredicate = (NameSubjectPredicate) other;
+ return name.equals(otherNameSubjectPredicate.name) && subject.equals(otherNameSubjectPredicate.subject);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("name", name).add("subject", subject).toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/PaidPredicate.java b/src/main/java/seedu/address/model/person/PaidPredicate.java
new file mode 100644
index 00000000000..9f801909920
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/PaidPredicate.java
@@ -0,0 +1,33 @@
+package seedu.address.model.person;
+
+import java.util.function.Predicate;
+
+/**
+ * Tests whether a person is unpaid.
+ */
+public class PaidPredicate implements Predicate {
+
+ private final boolean paid;
+ public PaidPredicate(boolean paid) {
+ this.paid = paid;
+ }
+ @Override
+ public boolean test(Person person) {
+ return !person.getPaid();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof PaidPredicate)) {
+ return false;
+ }
+
+ PaidPredicate otherPaidPredicate = (PaidPredicate) other;
+ return this.paid == otherPaidPredicate.paid;
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/PayRate.java b/src/main/java/seedu/address/model/person/PayRate.java
new file mode 100644
index 00000000000..64de38c4fba
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/PayRate.java
@@ -0,0 +1,71 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+
+/**
+ * Represents a Person's Pay Rate in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidPayRate(String)}
+ */
+public class PayRate {
+
+ public static final String MESSAGE_CONSTRAINTS = "PayRate can be either integers"
+ + "or decimals of up to 2 decimal places. It cannot be negative";
+
+ /*
+ * The first character of the address must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ * Only accepts positive numbers of up to 2 dp.
+ */
+ public static final String VALIDATION_REGEX = "^\\s*\\d+(\\.\\d{1,2})?\\s*$";
+
+ public final double value;
+
+ /**
+ * Constructs an {@code PayRate}.
+ *
+ * @param payRate A valid pay rate per hour.
+ */
+ public PayRate(String payRate) {
+ requireNonNull(payRate);
+ checkArgument(isValidPayRate(payRate), MESSAGE_CONSTRAINTS);
+ value = Double.parseDouble(payRate);
+ assert value >= 0.0 : "payrate cannot be negative";
+ }
+
+ public static boolean isValidPayRate(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%.2f", value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof PayRate)) {
+ return false;
+ }
+
+ PayRate otherPayRate = (PayRate) other;
+ return value == otherPayRate.value;
+ }
+
+ @Override
+ public int hashCode() {
+ Double payRate = value;
+ return payRate.hashCode();
+ }
+
+ public double getValue() {
+ return value;
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index abe8c46b535..c85d798b181 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -2,14 +2,15 @@
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-import java.util.Collections;
-import java.util.HashSet;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
import java.util.Objects;
-import java.util.Set;
+import java.util.logging.Logger;
+import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.model.tag.Tag;
-
/**
* Represents a Person in the address book.
* Guarantees: details are present and not null, field values are validated, immutable.
@@ -23,46 +24,159 @@ public class Person {
// Data fields
private final Address address;
- private final Set tags = new HashSet<>();
+ private final Subject subject;
+ private final Lesson lesson;
+ private boolean paid;
+ private PayRate payRate;
+ private Date beginTime;
+ private Date endTime;
+
+ private final Logger logger = LogsCenter.getLogger(getClass());
/**
* Every field must be present and not null.
*/
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
+
+ public Person(Name name, Phone phone, Email email, Address address, Subject subject, Day day,
+ Begin begin, End end, boolean paid, PayRate payRate) {
+ requireAllNonNull(name, phone, email, address, subject, day, begin, end, paid, payRate);
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
- this.tags.addAll(tags);
+ this.subject = subject;
+ this.paid = paid;
+ this.payRate = payRate;
+ this.lesson = new Lesson(day, begin, end);
+
+ try {
+ this.beginTime = convertTime(begin.toString());
+ this.endTime = convertTime(end.toString());
+ } catch (ParseException e) {
+ logger.info("[Person constructor]: Error parsing begin and end.");
+ throw new RuntimeException("Error parsing begin and end.");
+ }
+
}
+ /**
+ * @return a defensive copy of name
+ */
public Name getName() {
- return name;
+ return name.copy();
}
+ /**
+ * @return a defensive copy of phone
+ */
public Phone getPhone() {
- return phone;
+ return phone.copy();
}
+ /**
+ * @return a defensive copy of email
+ */
public Email getEmail() {
- return email;
+ return email.copy();
}
+ /**
+ * @return a defensive copy of address
+ */
public Address getAddress() {
- return address;
+ return address.copy();
+ }
+
+ /**
+ * @return a defensive copy of subject
+ */
+ public Subject getSubject() {
+ return subject.copy();
+ }
+
+ /**
+ * Return defensive copy of day
+ * @return day
+ */
+ public Day getDay() {
+ String day = lesson.day.toString();
+ return new Day(day);
+ }
+
+ /**
+ * Return defensive copy of Begin
+ * @return begin
+ */
+ public Begin getBegin() {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmm");
+ String begin = lesson.begin.format(formatter);
+ return new Begin(begin);
+ }
+
+ /**
+ * Return defensive copy of end
+ * @return end
+ */
+ public End getEnd() {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmm");
+ String end = lesson.end.format(formatter);
+ return new End(end);
+ }
+
+ /**
+ * @return a defensive copy of lesson
+ */
+ public Lesson getLesson() {
+ return lesson.copy();
+ }
+
+ /**
+ * @return a defensive copy of beginTime
+ */
+ public Date getBeginTime() {
+ return (Date) beginTime.clone();
+ }
+
+ /**
+ * @return a defensive copy of endTime
+ */
+ public Date getEndTime() {
+ return (Date) endTime.clone();
}
/**
* Returns an immutable tag set, which throws {@code UnsupportedOperationException}
* if modification is attempted.
*/
- public Set getTags() {
- return Collections.unmodifiableSet(tags);
+ public boolean getPaid() {
+ return this.paid;
+ }
+
+ public void setPaid() {
+ this.paid = true;
+ }
+
+ public void setUnPaid() {
+ this.paid = false;
+ }
+
+ public PayRate getPayRate() {
+ return payRate;
+ }
+
+ /**
+ * converts a string time into a date object
+ * @param time
+ * @return date object of the time
+ * @throws ParseException
+ */
+ public Date convertTime(String time) throws ParseException {
+ SimpleDateFormat format = new SimpleDateFormat("HHmm");
+ return format.parse(time);
}
/**
- * Returns true if both persons have the same name.
+ * Returns true if both persons have the same name and phone number.
* This defines a weaker notion of equality between two persons.
*/
public boolean isSamePerson(Person otherPerson) {
@@ -71,7 +185,24 @@ public boolean isSamePerson(Person otherPerson) {
}
return otherPerson != null
- && otherPerson.getName().equals(getName());
+ && otherPerson.getName().equals(getName())
+ && otherPerson.getPhone().equals(getPhone());
+ }
+
+ /**
+ * checks for clashing schedules
+ * @param otherPerson other person to be checked
+ * @return boolean for whether schedules clash
+ */
+ public boolean isSameDate(Person otherPerson) {
+ if (otherPerson == this) {
+ return true;
+ }
+
+ return otherPerson != null
+ && otherPerson.getDay().equals(getDay())
+ && getBeginTime().before(otherPerson.getEndTime())
+ && otherPerson.getBeginTime().before(getEndTime());
}
/**
@@ -94,13 +225,15 @@ public boolean equals(Object other) {
&& phone.equals(otherPerson.phone)
&& email.equals(otherPerson.email)
&& address.equals(otherPerson.address)
- && tags.equals(otherPerson.tags);
+ && subject.equals(otherPerson.subject)
+ && lesson.equals(otherPerson.lesson)
+ && payRate.equals(otherPerson.payRate);
}
@Override
public int hashCode() {
// use this method for custom fields hashing instead of implementing your own
- return Objects.hash(name, phone, email, address, tags);
+ return Objects.hash(name, phone, email, address, subject, lesson, paid, payRate);
}
@Override
@@ -110,8 +243,16 @@ public String toString() {
.add("phone", phone)
.add("email", email)
.add("address", address)
- .add("tags", tags)
+ .add("subject", subject)
+ .add("lesson", lesson)
+ .add("paid", paid)
+ .add("payrate", payRate)
.toString();
}
+ public double getMonthlyFee() {
+ double monthlyFee = lesson.getMonthlyHours() * payRate.getValue();
+ assert monthlyFee >= 0 : "monthly revenue should not be negative";
+ return monthlyFee;
+ }
}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
index d733f63d739..492aa2dc161 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/person/Phone.java
@@ -33,6 +33,13 @@ public static boolean isValidPhone(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * @return a defensive copy of phone.
+ */
+ public Phone copy() {
+ return new Phone(this.value);
+ }
+
@Override
public String toString() {
return value;
diff --git a/src/main/java/seedu/address/model/person/Subject.java b/src/main/java/seedu/address/model/person/Subject.java
new file mode 100644
index 00000000000..ce1bcf987f7
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Subject.java
@@ -0,0 +1,73 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Person's Subject in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidSubject(String)} (String)}
+ */
+public class Subject {
+
+ public static final String MESSAGE_CONSTRAINTS = "Subject can take any values, and it should not be blank";
+
+ /*
+ * The first character of the subject must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "[^\\s].*";
+
+ public final String value;
+
+ /**
+ * Constructs an {@code Subject}.
+ *
+ * @param subject A valid subject.
+ */
+ public Subject(String subject) {
+ requireNonNull(subject);
+ checkArgument(isValidSubject(subject), MESSAGE_CONSTRAINTS);
+ value = subject;
+ }
+
+ /**
+ * Returns true if a given string is a valid email.
+ */
+ public static boolean isValidSubject(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ /**
+ * @return a defensive copy of subject
+ */
+
+ public Subject copy() {
+ return new Subject(this.value);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Subject)) {
+ return false;
+ }
+
+ Subject otherSubject = (Subject) other;
+ return value.equals(otherSubject.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/SubjectContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/SubjectContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..cfb72b5e04d
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/SubjectContainsKeywordsPredicate.java
@@ -0,0 +1,45 @@
+package seedu.address.model.person;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+import seedu.address.commons.util.ToStringBuilder;
+
+/**
+ * Tests that a {@code Person}'s {@code Subject} matches any of the keywords given.
+ */
+public class SubjectContainsKeywordsPredicate implements Predicate {
+ private final List keywords;
+
+ public SubjectContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return keywords.isEmpty() || keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getSubject().value, keyword));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof SubjectContainsKeywordsPredicate)) {
+ return false;
+ }
+
+ SubjectContainsKeywordsPredicate otherSubjectContainsKeywordsPredicate =
+ (SubjectContainsKeywordsPredicate) other;
+ return keywords.equals(otherSubjectContainsKeywordsPredicate.keywords);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this).add("keywords", keywords).toString();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
index cc0a68d79f9..9d918539210 100644
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ b/src/main/java/seedu/address/model/person/UniquePersonList.java
@@ -5,9 +5,12 @@
import java.util.Iterator;
import java.util.List;
+import java.util.stream.Collectors;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import seedu.address.model.interval.Interval;
+import seedu.address.model.person.exceptions.ClashingScheduleException;
import seedu.address.model.person.exceptions.DuplicatePersonException;
import seedu.address.model.person.exceptions.PersonNotFoundException;
@@ -36,6 +39,28 @@ public boolean contains(Person toCheck) {
return internalList.stream().anyMatch(toCheck::isSamePerson);
}
+ /**
+ * Returns true if the list contains a perosn that has a clashing schedule
+ */
+ public boolean checkSameDate(Person toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream()
+ .filter(person -> !person.isSamePerson(toCheck))
+ .anyMatch(toCheck::isSameDate);
+ }
+
+ /**
+ * Finds the list of timings which have the same day as the Interval from the address book
+ * @param interval
+ * @return list of timings
+ */
+ public List findInterval(Interval interval) {
+ requireNonNull(interval);
+ return internalList.stream()
+ .filter(person -> person.getDay().toString().equals(interval.getIntervalDay().toString()))
+ .map(person -> person.getLesson().getTimeSlot()).collect(Collectors.toList());
+ }
+
/**
* Adds a person to the list.
* The person must not already exist in the list.
@@ -45,6 +70,9 @@ public void add(Person toAdd) {
if (contains(toAdd)) {
throw new DuplicatePersonException();
}
+ if (checkSameDate(toAdd)) {
+ throw new ClashingScheduleException();
+ }
internalList.add(toAdd);
}
@@ -53,7 +81,7 @@ public void add(Person toAdd) {
* {@code target} must exist in the list.
* The person identity of {@code editedPerson} must not be the same as another existing person in the list.
*/
- public void setPerson(Person target, Person editedPerson) {
+ public void setPerson(Person target, Person editedPerson, boolean isEditingSchedule) {
requireAllNonNull(target, editedPerson);
int index = internalList.indexOf(target);
@@ -65,9 +93,50 @@ public void setPerson(Person target, Person editedPerson) {
throw new DuplicatePersonException();
}
+ if (isEditingSchedule && checkSameDate(editedPerson)) {
+ throw new ClashingScheduleException();
+ }
+
internalList.set(index, editedPerson);
}
+ /**
+ * Set the person as paid in the list.
+ * The person must exist in the list.
+ */
+ public void setPaid(Person toPaid) {
+ requireNonNull(toPaid);
+
+ int index = internalList.indexOf(toPaid);
+ Person p = new Person(toPaid.getName(), toPaid.getPhone(), toPaid.getEmail(), toPaid.getAddress(),
+ toPaid.getSubject(), toPaid.getDay(), toPaid.getBegin(),
+ toPaid.getEnd(), true, toPaid.getPayRate());
+ if (index != -1) {
+ internalList.set(index, p);
+ }
+ }
+
+ public boolean getPaid(Person toGet) {
+ int index = internalList.indexOf(toGet);
+ return internalList.get(index).getPaid();
+ }
+
+ /**
+ * Set the person as not paid in the list.
+ * The person must exist in the list.
+ */
+ public void setUnPaid(Person toUnPaid) {
+ requireNonNull(toUnPaid);
+
+ int index = internalList.indexOf(toUnPaid);
+ Person p = new Person(toUnPaid.getName(), toUnPaid.getPhone(), toUnPaid.getEmail(), toUnPaid.getAddress(),
+ toUnPaid.getSubject(), toUnPaid.getDay(), toUnPaid.getBegin(),
+ toUnPaid.getEnd(), false, toUnPaid.getPayRate());
+ if (index != -1) {
+ internalList.set(index, p);
+ }
+ }
+
/**
* Removes the equivalent person from the list.
* The person must exist in the list.
diff --git a/src/main/java/seedu/address/model/person/exceptions/ClashingScheduleException.java b/src/main/java/seedu/address/model/person/exceptions/ClashingScheduleException.java
new file mode 100644
index 00000000000..1b75eed2e58
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/exceptions/ClashingScheduleException.java
@@ -0,0 +1,10 @@
+package seedu.address.model.person.exceptions;
+
+/**
+ * Signals that the operation will result in clashing schedules
+ */
+public class ClashingScheduleException extends RuntimeException {
+ public ClashingScheduleException() {
+ super("Operation would result in clashing schedules");
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
deleted file mode 100644
index f1a0d4e233b..00000000000
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package seedu.address.model.tag;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Tag in the address book.
- * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)}
- */
-public class Tag {
-
- public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric";
- public static final String VALIDATION_REGEX = "\\p{Alnum}+";
-
- public final String tagName;
-
- /**
- * Constructs a {@code Tag}.
- *
- * @param tagName A valid tag name.
- */
- public Tag(String tagName) {
- requireNonNull(tagName);
- checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS);
- this.tagName = tagName;
- }
-
- /**
- * Returns true if a given string is a valid tag name.
- */
- public static boolean isValidTagName(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof Tag)) {
- return false;
- }
-
- Tag otherTag = (Tag) other;
- return tagName.equals(otherTag.tagName);
- }
-
- @Override
- public int hashCode() {
- return tagName.hashCode();
- }
-
- /**
- * Format state as text for viewing.
- */
- public String toString() {
- return '[' + tagName + ']';
- }
-
-}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..cf9867e9af1 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -1,18 +1,17 @@
package seedu.address.model.util;
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
-
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Begin;
+import seedu.address.model.person.Day;
import seedu.address.model.person.Email;
+import seedu.address.model.person.End;
import seedu.address.model.person.Name;
+import seedu.address.model.person.PayRate;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
+import seedu.address.model.person.Subject;
/**
* Contains utility methods for populating {@code AddressBook} with sample data.
*/
@@ -20,23 +19,24 @@ public class SampleDataUtil {
public static Person[] getSamplePersons() {
return new Person[] {
new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
- new Address("Blk 30 Geylang Street 29, #06-40"),
- getTagSet("friends")),
+ new Address("Blk 30 Geylang Street 29, #06-40"), new Subject("Maths"), new Day("Mon"),
+ new Begin("2000"), new End("2100"), false, new PayRate("20")),
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 Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new Subject("Physics"), new Day("Tue"),
+ new Begin("1000"), new End("1100"), false, new PayRate("25.00")),
+
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 Address("Blk 11 Ang Mo Kio Street 74, #11-04"), new Subject("Chemistry"), new Day("Wed"),
+ new Begin("1200"), new End("1300"), false, new PayRate("30.00")),
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 Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new Subject("Biology"), new Day("Thu"),
+ new Begin("1700"), new End("1800"), false, new PayRate("40.00")),
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 Address("Blk 47 Tampines Street 20, #17-35"), new Subject("English"), new Day("Fri"),
+ new Begin("0900"), new End("1000"), false, new PayRate("50.00")),
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"))
+ new Address("Blk 45 Aljunied Street 85, #11-31"), new Subject("Chinese"), new Day("Sat"),
+ new Begin("0800"), new End("0900"), false, new PayRate("66.00"))
};
}
@@ -47,14 +47,4 @@ public static ReadOnlyAddressBook getSampleAddressBook() {
}
return sampleAb;
}
-
- /**
- * Returns a tag set containing the list of strings given.
- */
- public static Set getTagSet(String... strings) {
- return Arrays.stream(strings)
- .map(Tag::new)
- .collect(Collectors.toSet());
- }
-
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index bd1ca0f56c8..4224a762700 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -1,22 +1,19 @@
package seedu.address.storage;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Begin;
+import seedu.address.model.person.Day;
import seedu.address.model.person.Email;
+import seedu.address.model.person.End;
import seedu.address.model.person.Name;
+import seedu.address.model.person.PayRate;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
+import seedu.address.model.person.Subject;
/**
* Jackson-friendly version of {@link Person}.
*/
@@ -28,7 +25,13 @@ class JsonAdaptedPerson {
private final String phone;
private final String email;
private final String address;
- private final List tags = new ArrayList<>();
+ private final String subject;
+ private final String day;
+ private final String begin;
+ private final String end;
+ private final boolean paid;
+
+ private final String payRate;
/**
* Constructs a {@code JsonAdaptedPerson} with the given person details.
@@ -36,14 +39,19 @@ class JsonAdaptedPerson {
@JsonCreator
public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
@JsonProperty("email") String email, @JsonProperty("address") String address,
- @JsonProperty("tags") List tags) {
+ @JsonProperty("subject") String subject, @JsonProperty("day") String day,
+ @JsonProperty("begin") String begin, @JsonProperty("end") String end,
+ @JsonProperty("paid") Boolean paid, @JsonProperty("payrate") String payRate) {
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
- if (tags != null) {
- this.tags.addAll(tags);
- }
+ this.subject = subject;
+ this.day = day;
+ this.begin = begin;
+ this.end = end;
+ this.paid = paid;
+ this.payRate = payRate;
}
/**
@@ -54,9 +62,12 @@ public JsonAdaptedPerson(Person source) {
phone = source.getPhone().value;
email = source.getEmail().value;
address = source.getAddress().value;
- tags.addAll(source.getTags().stream()
- .map(JsonAdaptedTag::new)
- .collect(Collectors.toList()));
+ subject = source.getSubject().value;
+ day = source.getDay().value.toString();
+ begin = source.getBegin().value;
+ end = source.getEnd().value;
+ paid = source.getPaid();
+ payRate = source.getPayRate().toString();
}
/**
@@ -65,11 +76,6 @@ public JsonAdaptedPerson(Person source) {
* @throws IllegalValueException if there were any data constraints violated in the adapted person.
*/
public Person toModelType() throws IllegalValueException {
- final List personTags = new ArrayList<>();
- for (JsonAdaptedTag tag : tags) {
- personTags.add(tag.toModelType());
- }
-
if (name == null) {
throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
}
@@ -102,8 +108,48 @@ public Person toModelType() throws IllegalValueException {
}
final Address modelAddress = new Address(address);
- final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ if (subject == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Subject.class.getSimpleName()));
+ }
+ if (!Subject.isValidSubject(subject)) {
+ throw new IllegalValueException(Subject.MESSAGE_CONSTRAINTS);
+ }
+ final Subject modelSubject = new Subject(subject);
+
+ if (day == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Day.class.getSimpleName()));
+ }
+ if (!Day.isValidDay(day)) {
+ throw new IllegalValueException(Day.MESSAGE_CONSTRAINTS);
+ }
+ final Day modelDay = new Day(day);
+
+ if (begin == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Begin.class.getSimpleName()));
+ }
+ if (!Begin.isValidBegin(begin)) {
+ throw new IllegalValueException(Begin.MESSAGE_CONSTRAINTS);
+ }
+ final Begin modelBegin = new Begin(begin);
+
+ if (end == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, End.class.getSimpleName()));
+ }
+ if (!End.isValidEnd(end)) {
+ throw new IllegalValueException(End.MESSAGE_CONSTRAINTS);
+ }
+ final End modelEnd = new End(end);
+
+ if (payRate == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, PayRate.class.getSimpleName()));
+ }
+ if (!PayRate.isValidPayRate(payRate)) {
+ throw new IllegalValueException(PayRate.MESSAGE_CONSTRAINTS);
+ }
+
+ final PayRate modelPayRate = new PayRate(payRate);
+ return new Person(modelName, modelPhone, modelEmail, modelAddress, modelSubject, modelDay,
+ modelBegin, modelEnd, paid, modelPayRate);
}
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java
deleted file mode 100644
index 0df22bdb754..00000000000
--- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java
+++ /dev/null
@@ -1,48 +0,0 @@
-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.tag.Tag;
-
-/**
- * Jackson-friendly version of {@link Tag}.
- */
-class JsonAdaptedTag {
-
- private final String tagName;
-
- /**
- * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}.
- */
- @JsonCreator
- public JsonAdaptedTag(String tagName) {
- this.tagName = tagName;
- }
-
- /**
- * Converts a given {@code Tag} into this class for Jackson use.
- */
- public JsonAdaptedTag(Tag source) {
- tagName = source.tagName;
- }
-
- @JsonValue
- public String getTagName() {
- return tagName;
- }
-
- /**
- * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object.
- *
- * @throws IllegalValueException if there were any data constraints violated in the adapted tag.
- */
- public Tag toModelType() throws IllegalValueException {
- if (!Tag.isValidTagName(tagName)) {
- throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS);
- }
- return new Tag(tagName);
- }
-
-}
diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java
index 9e75478664b..6a61ad45ebf 100644
--- a/src/main/java/seedu/address/ui/CommandBox.java
+++ b/src/main/java/seedu/address/ui/CommandBox.java
@@ -38,7 +38,7 @@ public CommandBox(CommandExecutor commandExecutor) {
private void handleCommandEntered() {
String commandText = commandTextField.getText();
if (commandText.equals("")) {
- return;
+ setStyleToIndicateCommandFailure();
}
try {
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 3f16b2fcf26..8ee33be0aab 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 {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
+ public static final String USERGUIDE_URL = "https://ay2324s1-cs2103t-f10-4.github.io/tp/UserGuide.html";
public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 79e74ef37c0..984a9890e29 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -34,6 +34,7 @@ public class MainWindow extends UiPart {
private PersonListPanel personListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
+ private ScheduleListPanel scheduleListPanel;
@FXML
private StackPane commandBoxPlaceholder;
@@ -50,6 +51,9 @@ public class MainWindow extends UiPart {
@FXML
private StackPane statusbarPlaceholder;
+ @FXML
+ private StackPane scheduleListPanelPlaceholder;
+
/**
* Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}.
*/
@@ -121,6 +125,10 @@ void fillInnerParts() {
CommandBox commandBox = new CommandBox(this::executeCommand);
commandBoxPlaceholder.getChildren().add(commandBox.getRoot());
+
+ scheduleListPanel = new ScheduleListPanel(logic.getScheduleList());
+ scheduleListPanelPlaceholder.getChildren().add(scheduleListPanel.getRoot());
+
}
/**
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 094c42cda82..e67a9cfc40b 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -1,7 +1,5 @@
package seedu.address.ui;
-import java.util.Comparator;
-
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
@@ -38,6 +36,16 @@ public class PersonCard extends UiPart {
private Label address;
@FXML
private Label email;
+ @FXML
+ private Label subject;
+
+ @FXML
+ private Label lesson;
+ @FXML
+ private Label paid;
+ @FXML
+ private Label payRate;
+
@FXML
private FlowPane tags;
@@ -52,8 +60,19 @@ public PersonCard(Person person, int displayedIndex) {
phone.setText(person.getPhone().value);
address.setText(person.getAddress().value);
email.setText(person.getEmail().value);
- person.getTags().stream()
- .sorted(Comparator.comparing(tag -> tag.tagName))
- .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+ subject.setText(person.getSubject().value);
+ lesson.setText(person.getLesson().toString());
+ payRate.setText("Rate: " + person.getPayRate().toString() + "/h");
+
+ if (person.getPaid()) {
+ paid.setText("paid");
+ paid.setStyle("-fx-background-color: green; -fx-text-fill: white;");
+ } else {
+ paid.setText("not paid");
+ paid.setStyle("-fx-background-color: red; -fx-text-fill: white;");
+
+ }
+ tags.getChildren().add(paid);
+ tags.getChildren().add(subject);
}
}
diff --git a/src/main/java/seedu/address/ui/ScheduleCard.java b/src/main/java/seedu/address/ui/ScheduleCard.java
new file mode 100644
index 00000000000..d165d0e41f2
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ScheduleCard.java
@@ -0,0 +1,49 @@
+package seedu.address.ui;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import seedu.address.model.person.Person;
+
+
+/**
+ * An UI component that displays schedule of a {@code Person}.
+ */
+public class ScheduleCard extends UiPart {
+
+ private static final String FXML = "ScheduleCard.fxml";
+
+ public final Person person;
+ @FXML
+ private HBox cardPane;
+
+ @FXML
+ private Label name;
+
+ @FXML
+ private Label address;
+
+ @FXML
+ private Label lesson;
+
+ @FXML
+ private Label subject;
+
+ @FXML
+ private FlowPane tags;
+
+ /**
+ * Creates a {@code PersonCode} with the given {@code Person} and index to display.
+ */
+ public ScheduleCard(Person person) {
+ super(FXML);
+ this.person = person;
+ name.setText(person.getName().fullName);
+ address.setText(person.getAddress().value);
+ lesson.setText(person.getLesson().toString());
+ subject.setText(person.getSubject().toString());
+ tags.getChildren().add(subject);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/ScheduleListPanel.java b/src/main/java/seedu/address/ui/ScheduleListPanel.java
new file mode 100644
index 00000000000..f8c380e9446
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ScheduleListPanel.java
@@ -0,0 +1,53 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.person.Person;
+
+
+/**
+ * Panel containing the list of persons' schedules.
+ */
+public class ScheduleListPanel extends UiPart {
+
+ private static final String FXML = "ScheduleListPanel.fxml";
+
+ @FXML
+ private ListView scheduleListView;
+
+ private final Logger logger = LogsCenter.getLogger(ScheduleListPanel.class);
+
+ /**
+ * Creates a {@code ScheduleListPanel} with the given {@code ObservableList}.
+ */
+ public ScheduleListPanel(ObservableList personList) {
+ super(FXML);
+ scheduleListView.setItems(personList);
+ scheduleListView.setCellFactory(listView -> new ScheduleListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}.
+ */
+ class ScheduleListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Person person, boolean empty) {
+ super.updateItem(person, empty);
+
+ if (empty || person == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new ScheduleCard(person).getRoot());
+ }
+ }
+ }
+
+
+}
diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java
index fdf024138bc..b7c0f17b5db 100644
--- a/src/main/java/seedu/address/ui/UiManager.java
+++ b/src/main/java/seedu/address/ui/UiManager.java
@@ -20,7 +20,7 @@ public class UiManager implements Ui {
public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane";
private static final Logger logger = LogsCenter.getLogger(UiManager.class);
- private static final String ICON_APPLICATION = "/images/address_book_32.png";
+ private static final String ICON_APPLICATION = "/images/tuition_connect.png";
private Logic logic;
private MainWindow mainWindow;
@@ -39,6 +39,8 @@ public void start(Stage primaryStage) {
//Set the application icon.
primaryStage.getIcons().add(getImage(ICON_APPLICATION));
+
+
try {
mainWindow = new MainWindow(primaryStage, logic);
mainWindow.show(); //This should be called before creating other UI parts
diff --git a/src/main/resources/images/PaidSequenceDigram.png b/src/main/resources/images/PaidSequenceDigram.png
new file mode 100644
index 00000000000..50195b79d41
Binary files /dev/null and b/src/main/resources/images/PaidSequenceDigram.png differ
diff --git a/src/main/resources/images/UnpaidAllSequenceDigram.png b/src/main/resources/images/UnpaidAllSequenceDigram.png
new file mode 100644
index 00000000000..575fe997bed
Binary files /dev/null and b/src/main/resources/images/UnpaidAllSequenceDigram.png differ
diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png
deleted file mode 100644
index 29810cf1fd9..00000000000
Binary files a/src/main/resources/images/address_book_32.png and /dev/null differ
diff --git a/src/main/resources/images/tuition_connect.png b/src/main/resources/images/tuition_connect.png
new file mode 100644
index 00000000000..6cae74b6489
Binary files /dev/null and b/src/main/resources/images/tuition_connect.png differ
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..07f94e5093f 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -89,22 +89,22 @@
.list-view {
-fx-background-insets: 0;
- -fx-padding: 0;
+ -fx-padding: 10;
-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;
+ -fx-graphic-text-gap : 10;
+ -fx-padding: 0;
}
.list-cell:filled:even {
- -fx-background-color: #3c3e3f;
+ -fx-background-color: derive(#1d1d1d, 20%);
}
.list-cell:filled:odd {
- -fx-background-color: #515658;
+ -fx-background-color: derive(#1d1d1d, 20%);
}
.list-cell:filled:selected {
@@ -122,7 +122,7 @@
.cell_big_label {
-fx-font-family: "Segoe UI Semibold";
- -fx-font-size: 16px;
+ -fx-font-size: 20px;
-fx-text-fill: #010504;
}
@@ -131,6 +131,11 @@
-fx-font-size: 13px;
-fx-text-fill: #010504;
}
+.custom-vbox {
+ -fx-background-color: grey;
+ -fx-background-radius: 10;
+ -fx-padding: 10;
+}
.stack-pane {
-fx-background-color: derive(#1d1d1d, 20%);
@@ -147,10 +152,16 @@
}
.result-display {
- -fx-background-color: transparent;
+ -fx-background-insets: 0;
+ -fx-border-color: #383838 #383838 #383838 #383838;
+ -fx-border-insets: 0;
-fx-font-family: "Segoe UI Light";
-fx-font-size: 13pt;
-fx-text-fill: white;
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-radius: 10;
+ -fx-padding: 10;
+ -fx-border-radius: 10 10 10 10;
}
.result-display .label {
@@ -318,14 +329,16 @@
}
#commandTextField {
- -fx-background-color: transparent #383838 transparent #383838;
-fx-background-insets: 0;
- -fx-border-color: #383838 #383838 #ffffff #383838;
+ -fx-border-color: #383838 #383838 #383838 #383838;
-fx-border-insets: 0;
- -fx-border-width: 1;
-fx-font-family: "Segoe UI Light";
-fx-font-size: 13pt;
-fx-text-fill: white;
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-background-radius: 10;
+ -fx-padding: 10;
+ -fx-border-radius: 10 10 10 10;
}
#filterField, #personListPanel, #personWebpage {
@@ -333,7 +346,7 @@
}
#resultDisplay .content {
- -fx-background-color: transparent, #383838, transparent, #383838;
+ -fx-background-color: derive(#1d1d1d, 30%);
-fx-background-radius: 0;
}
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index 7778f666a0a..1b2a932c5be 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -10,11 +10,12 @@
+
+ title="TuitionConnect" minWidth="450" minHeight="600" onCloseRequest="#handleExit">
-
+
@@ -50,7 +51,10 @@
-
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index f5e812e25e6..990f2a4ba38 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -10,11 +10,14 @@
+
+
+
-
+
@@ -27,10 +30,17 @@
-
+
+
-
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml
index 01b691792a9..598885b7423 100644
--- a/src/main/resources/view/ResultDisplay.fxml
+++ b/src/main/resources/view/ResultDisplay.fxml
@@ -3,7 +3,7 @@
-
-
+
diff --git a/src/main/resources/view/ScheduleCard.fxml b/src/main/resources/view/ScheduleCard.fxml
new file mode 100644
index 00000000000..119b9d8ffc6
--- /dev/null
+++ b/src/main/resources/view/ScheduleCard.fxml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/ScheduleListPanel.fxml b/src/main/resources/view/ScheduleListPanel.fxml
new file mode 100644
index 00000000000..88888dd2e1d
--- /dev/null
+++ b/src/main/resources/view/ScheduleListPanel.fxml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
index 6a4d2b7181c..3831fb4f4b6 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json
@@ -3,11 +3,15 @@
"name": "Valid Person",
"phone": "9482424",
"email": "hans@example.com",
- "address": "4th street"
+ "address": "4th street",
+ "day": "Monday",
+ "paid": "false"
}, {
"name": "Person With Invalid Phone Field",
"phone": "948asdf2424",
"email": "hans@example.com",
- "address": "4th street"
+ "address": "4th street",
+ "day": "Monday",
+ "paid": "false"
} ]
}
diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
index ccd21f7d1a9..a7a4d680b5d 100644
--- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json
@@ -3,6 +3,8 @@
"name": "Person with invalid name field: Ha!ns Mu@ster",
"phone": "9482424",
"email": "hans@example.com",
- "address": "4th street"
+ "address": "4th street",
+ "day": "Monday",
+ "paid": "false"
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
index a7427fe7aa2..6d61a12f569 100644
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
@@ -4,11 +4,23 @@
"phone": "94351253",
"email": "alice@example.com",
"address": "123, Jurong West Ave 6, #08-111",
- "tags": [ "friends" ]
+ "subject" : "Maths",
+ "day": "Mon",
+ "begin": "0800",
+ "end": "1000",
+ "tags": [ "friends" ],
+ "paid": "false",
+ "payrate": "20.00"
}, {
"name": "Alice Pauline",
"phone": "94351253",
"email": "pauline@example.com",
- "address": "4th street"
+ "subject" : "Maths",
+ "day": "Mon",
+ "begin": "0800",
+ "end": "1000",
+ "address": "4th street",
+ "paid": "false",
+ "payrate": "20.00"
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
index ad3f135ae42..ee6ca807f53 100644
--- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
@@ -3,6 +3,8 @@
"name": "Hans Muster",
"phone": "9482424",
"email": "invalid@email!3e",
- "address": "4th street"
+ "day": "Monday",
+ "address": "4th street",
+ "paid": "false"
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
index 72262099d35..8111cf20063 100644
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
@@ -5,42 +5,84 @@
"phone" : "94351253",
"email" : "alice@example.com",
"address" : "123, Jurong West Ave 6, #08-111",
- "tags" : [ "friends" ]
+ "subject" : "Maths",
+ "day": "Mon",
+ "begin": "0800",
+ "end": "1000",
+ "tags" : [ "friends" ],
+ "paid": "false",
+ "payrate": "20.00"
}, {
"name" : "Benson Meier",
"phone" : "98765432",
"email" : "johnd@example.com",
"address" : "311, Clementi Ave 2, #02-25",
- "tags" : [ "owesMoney", "friends" ]
+ "subject" : "Chemistry",
+ "day": "Tue",
+ "begin": "0900",
+ "end": "1100",
+ "tags" : [ "owesMoney", "friends" ],
+ "paid": "false",
+ "payrate": "15.00"
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
"email" : "heinz@example.com",
"address" : "wall street",
- "tags" : [ ]
+ "subject" : "Biology",
+ "day": "Thu",
+ "begin": "1800",
+ "end": "2000",
+ "tags" : [ ],
+ "paid": "false",
+ "payrate": "25.00"
}, {
"name" : "Daniel Meier",
"phone" : "87652533",
"email" : "cornelia@example.com",
"address" : "10th street",
- "tags" : [ "friends" ]
+ "subject" : "Physics",
+ "day": "Thu",
+ "begin": "1100",
+ "end": "1300",
+ "tags" : [ "friends" ],
+ "paid": "false",
+ "payrate": "35.00"
}, {
"name" : "Elle Meyer",
"phone" : "9482224",
"email" : "werner@example.com",
"address" : "michegan ave",
- "tags" : [ ]
+ "subject" : "Maths",
+ "day": "Fri",
+ "begin": "1200",
+ "end": "1400",
+ "tags" : [ ],
+ "paid": "false",
+ "payrate": "50.00"
}, {
"name" : "Fiona Kunz",
"phone" : "9482427",
"email" : "lydia@example.com",
"address" : "little tokyo",
- "tags" : [ ]
+ "subject" : "English",
+ "day": "Sat",
+ "begin": "1300",
+ "end": "1500",
+ "tags" : [ ],
+ "paid": "false",
+ "payrate": "40.00"
}, {
"name" : "George Best",
"phone" : "9482442",
"email" : "anna@example.com",
"address" : "4th street",
- "tags" : [ ]
+ "subject" : "Chinese",
+ "day": "Sun",
+ "begin": "1400",
+ "end": "1620",
+ "tags" : [ ],
+ "paid": "false",
+ "payrate": "30.00"
} ]
}
diff --git a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json
index 1037548a9cd..3ac371a8f6f 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" : "tuitionconnect.json"
}
diff --git a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json
index b819bed900a..77315334fc4 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" : "tuitionconnect.json"
}
diff --git a/src/test/java/seedu/address/commons/util/AppUtilTest.java b/src/test/java/seedu/address/commons/util/AppUtilTest.java
index 594de1e6365..65c991bab31 100644
--- a/src/test/java/seedu/address/commons/util/AppUtilTest.java
+++ b/src/test/java/seedu/address/commons/util/AppUtilTest.java
@@ -9,7 +9,7 @@ public class AppUtilTest {
@Test
public void getImage_exitingImage() {
- assertNotNull(AppUtil.getImage("/images/address_book_32.png"));
+ assertNotNull(AppUtil.getImage("/images/tuition_connect.png"));
}
@Test
diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/address/commons/util/StringUtilTest.java
index c56d407bf3f..458ab21fe64 100644
--- a/src/test/java/seedu/address/commons/util/StringUtilTest.java
+++ b/src/test/java/seedu/address/commons/util/StringUtilTest.java
@@ -109,7 +109,7 @@ public void containsWordIgnoreCase_validInputs_correctResult() {
assertFalse(StringUtil.containsWordIgnoreCase(" ", "123"));
// Matches a partial word only
- assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bb")); // Sentence word bigger than query word
+ assertTrue(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bb")); // Sentence word bigger than query word
assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bbbb")); // Query word bigger than sentence word
// Matches word in the sentence, different upper/lower case letters
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index baf8ce336a2..a69fe2b189a 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -1,12 +1,18 @@
package seedu.address.logic;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX;
import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.BEGIN_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.DAY_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.END_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.PAYRATE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_AMY;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.AMY;
@@ -20,6 +26,7 @@
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
@@ -46,7 +53,7 @@ public class LogicManagerTest {
@BeforeEach
public void setUp() {
JsonAddressBookStorage addressBookStorage =
- new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json"));
+ new JsonAddressBookStorage(temporaryFolder.resolve("tuitionconnect.json"));
JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json"));
StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
logic = new LogicManager(model, storage);
@@ -58,10 +65,16 @@ public void execute_invalidCommandFormat_throwsParseException() {
assertParseException(invalidCommand, MESSAGE_UNKNOWN_COMMAND);
}
+ @Test
+ public void execute_emptyCommandFormat_throwsParseException() {
+ String emptyCommand = "";
+ assertParseException(emptyCommand, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
+ }
+
@Test
public void execute_commandExecutionError_throwsCommandException() {
String deleteCommand = "delete 9";
- assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandException(deleteCommand, MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
}
@Test
@@ -71,7 +84,7 @@ public void execute_validCommand_success() throws Exception {
}
@Test
- public void execute_storageThrowsIoException_throwsCommandException() {
+ public void execute_storageThrowsIoException_throwsParseException() {
assertCommandFailureForExceptionFromStorage(DUMMY_IO_EXCEPTION, String.format(
LogicManager.FILE_OPS_ERROR_FORMAT, DUMMY_IO_EXCEPTION.getMessage()));
}
@@ -166,8 +179,9 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath)
// Triggers the saveAddressBook method by executing an 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();
+ + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + SUBJECT_DESC_AMY + DAY_DESC_AMY
+ + BEGIN_DESC_AMY + END_DESC_AMY + PAYRATE_DESC_AMY;
+ Person expectedPerson = new PersonBuilder(AMY).build();
ModelManager expectedModel = new ModelManager();
expectedModel.addPerson(expectedPerson);
assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
index 90e8253f48e..ffa543b4cc8 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
@@ -7,22 +7,17 @@
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
-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.Messages;
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.ModelStub;
import seedu.address.testutil.PersonBuilder;
public class AddCommandTest {
@@ -53,6 +48,46 @@ public void execute_duplicatePerson_throwsCommandException() {
assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub));
}
+ @Test
+ public void execute_clashingDates_throwsCommandException() {
+ Person validPerson = new PersonBuilder().build();
+ ModelStub modelStub = new ModelStubWithPerson(validPerson);
+
+ Person clashingSchedule = new PersonBuilder().withDay(validPerson.getDay().toString())
+ .withName(ALICE.getName().toString())
+ .withBegin(validPerson.getBegin().toString())
+ .withEnd(validPerson.getEnd().toString())
+ .build();
+ AddCommand addCommand = new AddCommand(clashingSchedule);
+ assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_DATE, () -> addCommand.execute(modelStub));
+ }
+
+ @Test
+ public void execute_beginBeforeEnd_addSuccessful() throws Exception {
+ ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded();
+
+ Person validPerson = new PersonBuilder()
+ .withBegin("1500")
+ .withEnd("1600")
+ .build();
+
+ CommandResult commandResult = new AddCommand(validPerson).execute(modelStub);
+
+ assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, Messages.format(validPerson)),
+ commandResult.getFeedbackToUser());
+ assertEquals(Arrays.asList(validPerson), modelStub.personsAdded);
+ }
+
+ @Test
+ public void execute_beginAfterEnd_throwsAssertionException() {
+ assertThrows(AssertionError.class, () -> {
+ new PersonBuilder()
+ .withBegin("2200")
+ .withEnd("2100")
+ .build();;
+ });
+ }
+
@Test
public void equals() {
Person alice = new PersonBuilder().withName("Alice").build();
@@ -84,81 +119,6 @@ public void toStringMethod() {
assertEquals(expected, addCommand.toString());
}
- /**
- * A default model stub that have all of the methods failing.
- */
- private class ModelStub implements Model {
- @Override
- public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public ReadOnlyUserPrefs getUserPrefs() {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public GuiSettings getGuiSettings() {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public void setGuiSettings(GuiSettings guiSettings) {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public Path getAddressBookFilePath() {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public void setAddressBookFilePath(Path addressBookFilePath) {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public void addPerson(Person person) {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public void setAddressBook(ReadOnlyAddressBook newData) {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public ReadOnlyAddressBook getAddressBook() {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public boolean hasPerson(Person person) {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public void deletePerson(Person target) {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public void setPerson(Person target, Person editedPerson) {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public ObservableList getFilteredPersonList() {
- throw new AssertionError("This method should not be called.");
- }
-
- @Override
- public void updateFilteredPersonList(Predicate predicate) {
- throw new AssertionError("This method should not be called.");
- }
- }
-
/**
* A Model stub that contains a single person.
*/
@@ -175,6 +135,12 @@ public boolean hasPerson(Person person) {
requireNonNull(person);
return this.person.isSamePerson(person);
}
+
+ @Override
+ public boolean hasDate(Person person) {
+ requireNonNull(person);
+ return this.person.isSameDate(person);
+ }
}
/**
@@ -189,6 +155,12 @@ public boolean hasPerson(Person person) {
return personsAdded.stream().anyMatch(person::isSamePerson);
}
+ @Override
+ public boolean hasDate(Person person) {
+ requireNonNull(person);
+ return personsAdded.stream().anyMatch(person::isSameDate);
+ }
+
@Override
public void addPerson(Person person) {
requireNonNull(person);
@@ -199,6 +171,12 @@ public void addPerson(Person person) {
public ReadOnlyAddressBook getAddressBook() {
return new AddressBook();
}
+
+ @Override
+ public void purgeAddressBook() {}
+
+ @Override
+ public void commitAddressBook() {}
}
}
diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
index 643a1d08069..25f1d7a9e7c 100644
--- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
+++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
@@ -3,10 +3,15 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
import static seedu.address.testutil.Assert.assertThrows;
import java.util.ArrayList;
@@ -17,10 +22,14 @@
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
+import seedu.address.model.interval.Duration;
+import seedu.address.model.interval.Interval;
+import seedu.address.model.interval.IntervalBegin;
+import seedu.address.model.interval.IntervalDay;
+import seedu.address.model.interval.IntervalEnd;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
-
/**
* Contains helper methods for testing commands.
*/
@@ -34,8 +43,16 @@ public class CommandTestUtil {
public static final String VALID_EMAIL_BOB = "bob@example.com";
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_SUBJECT_AMY = "Maths";
+ public static final String VALID_SUBJECT_BOB = "Physics";
+ public static final String VALID_DAY_AMY = "Mon";
+ public static final String VALID_DAY_BOB = "Tue";
+ public static final String VALID_BEGIN_AMY = "1200";
+ public static final String VALID_BEGIN_BOB = "1300";
+ public static final String VALID_END_AMY = "1400";
+ public static final String VALID_END_BOB = "1500";
+ public static final String VALID_PAYRATE_AMY = "75.00";
+ public static final String VALID_PAYRATE_BOB = "25.00";
public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY;
public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB;
@@ -45,14 +62,26 @@ public class CommandTestUtil {
public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB;
public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY;
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 SUBJECT_DESC_AMY = " " + PREFIX_SUBJECT + VALID_SUBJECT_AMY;
+ public static final String SUBJECT_DESC_BOB = " " + PREFIX_SUBJECT + VALID_SUBJECT_BOB;
+ public static final String DAY_DESC_AMY = " " + PREFIX_DAY + VALID_DAY_AMY;
+ public static final String DAY_DESC_BOB = " " + PREFIX_DAY + VALID_DAY_BOB;
+ public static final String BEGIN_DESC_AMY = " " + PREFIX_BEGIN + VALID_BEGIN_AMY;
+ public static final String BEGIN_DESC_BOB = " " + PREFIX_BEGIN + VALID_BEGIN_BOB;
+ public static final String END_DESC_AMY = " " + PREFIX_END + VALID_END_AMY;
+ public static final String END_DESC_BOB = " " + PREFIX_END + VALID_END_BOB;
+ public static final String PAYRATE_DESC_AMY = " " + PREFIX_PAYRATE + VALID_PAYRATE_AMY;
+ public static final String PAYRATE_DESC_BOB = " " + PREFIX_PAYRATE + VALID_PAYRATE_BOB;
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_SUBJECT_DESC = " " + PREFIX_SUBJECT; // empty string not allowed for subjects
+ public static final String INVALID_DAY_DESC = " " + PREFIX_DAY + "Mond"; // full day name not allowed
+ public static final String INVALID_BEGIN_DESC = " " + PREFIX_BEGIN + "9999"; //not a valid time
+ public static final String INVALID_END_DESC = " " + PREFIX_END + "8888"; // not a valid time
+ public static final String INVALID_PAYRATE_DESC = " " + PREFIX_PAYRATE + "hundred";
public static final String PREAMBLE_WHITESPACE = "\t \r \n";
public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble";
@@ -63,19 +92,46 @@ public class CommandTestUtil {
static {
DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
.withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
- .withTags(VALID_TAG_FRIEND).build();
+ .withSubject(VALID_SUBJECT_AMY).withDay(VALID_DAY_AMY)
+ .withBegin(VALID_BEGIN_AMY).withEnd(VALID_END_AMY)
+ .withPayRate(VALID_PAYRATE_AMY).build();
DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
.withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB)
- .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
+ .withSubject(VALID_SUBJECT_BOB).withDay(VALID_DAY_BOB)
+ .withBegin(VALID_BEGIN_BOB).withEnd(VALID_END_BOB)
+ .withPayRate(VALID_PAYRATE_BOB).build();
}
+ public static final String VALID_INTERVAL_DAY = "Mon";
+ public static final String VALID_INTERVAL_DURATION = "60";
+ public static final String VALID_INTERVAL_BEGIN = "0800";
+ public static final String VALID_INTERVAL_END = "2200";
+
+ public static final String INVALID_INTERVAL_DAY = "Mond";
+ public static final String INVALID_INTERVAL_DURATION = "-60";
+ public static final String INVALID_INTERVAL_BEGIN = "8888";
+ public static final String INVALID_INTERVAL_END = "9999";
+
+ public static final Interval VALID_INTERVAL_ONE = new Interval(new IntervalDay("Mon"), new Duration("60"),
+ new IntervalBegin("0800"), new IntervalEnd("2200"));
+
+ public static final String INTERVAL_DAY_DESC_ONE = " " + PREFIX_DAY + VALID_INTERVAL_DAY;
+ public static final String INTERVAL_DURATION_DESC_ONE = " " + PREFIX_DURATION + VALID_INTERVAL_DURATION;
+ public static final String INTERVAL_BEGIN_DESC_ONE = " " + PREFIX_BEGIN + VALID_INTERVAL_BEGIN;
+ public static final String INTERVAL_END_DESC_ONE = " " + PREFIX_END + VALID_INTERVAL_END;
+
+ public static final String INVALID_INTERVAL_DAY_DESC_ONE = " " + PREFIX_DAY + INVALID_INTERVAL_DAY;
+ public static final String INVALID_INTERVAL_DURATION_DESC_ONE = " " + PREFIX_DURATION + INVALID_INTERVAL_DURATION;
+ public static final String INVALID_INTERVAL_BEGIN_DESC_ONE = " " + PREFIX_BEGIN + INVALID_INTERVAL_BEGIN;
+ public static final String INVALID_INTERVAL_END_DESC_ONE = " " + PREFIX_END + INVALID_INTERVAL_END;
+
/**
* Executes the given {@code command}, confirms that
* - the returned {@link CommandResult} matches {@code expectedCommandResult}
* - 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,7 +146,7 @@ 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) {
+ Model expectedModel) {
CommandResult expectedCommandResult = new CommandResult(expectedMessage);
assertCommandSuccess(command, actualModel, expectedCommandResult, expectedModel);
}
@@ -111,6 +167,7 @@ public static void assertCommandFailure(Command command, Model actualModel, Stri
assertEquals(expectedAddressBook, actualModel.getAddressBook());
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.
@@ -125,4 +182,21 @@ public static void showPersonAtIndex(Model model, Index targetIndex) {
assertEquals(1, model.getFilteredPersonList().size());
}
+ /**
+ * Simulates an add command
+ */
+ public static void simulateAddCommand(Model model, Person person) {
+ model.purgeAddressBook();
+ model.addPerson(person);
+ model.commitAddressBook();
+ }
+
+ /**
+ * Simulates a delete command
+ */
+ public static void simulateDeleteCommand(Model model, Person person) {
+ model.purgeAddressBook();
+ model.deletePerson(person);
+ model.commitAddressBook();
+ }
}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
index b6f332eabca..b309decbe3c 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
@@ -46,7 +46,7 @@ public void execute_invalidIndexUnfilteredList_throwsCommandException() {
Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
- assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
}
@Test
@@ -76,7 +76,7 @@ public void execute_invalidIndexFilteredList_throwsCommandException() {
DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
- assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
}
@Test
diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
index 469dd97daa7..d5c155d080c 100644
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
@@ -7,10 +7,10 @@
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.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.getTypicalAddressBook;
@@ -44,7 +44,7 @@ public void execute_allFieldsSpecifiedUnfilteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
- expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
+ expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson, true);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
@@ -55,17 +55,16 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() {
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();
+ Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB).build();
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
- .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build();
+ .withPhone(VALID_PHONE_BOB).build();
EditCommand editCommand = new EditCommand(indexLastPerson, descriptor);
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
- expectedModel.setPerson(lastPerson, editedPerson);
+ expectedModel.setPerson(lastPerson, editedPerson, false);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
@@ -82,6 +81,22 @@ public void execute_noFieldSpecifiedUnfilteredList_success() {
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
+ @Test
+ public void execute_validBeginAndEndTimes_success() {
+ Person validPerson = new PersonBuilder()
+ .withBegin("2100")
+ .withEnd("2200")
+ .build();
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(validPerson).build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+
+ String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(validPerson));
+
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ expectedModel.setPerson(model.getFilteredPersonList().get(0), validPerson, true);
+
+ assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ }
@Test
public void execute_filteredList_success() {
showPersonAtIndex(model, INDEX_FIRST_PERSON);
@@ -94,7 +109,7 @@ public void execute_filteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
- expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
+ expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson, false);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
@@ -126,7 +141,7 @@ public void execute_invalidPersonIndexUnfilteredList_failure() {
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build();
EditCommand editCommand = new EditCommand(outOfBoundIndex, descriptor);
- assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
}
/**
@@ -143,7 +158,17 @@ public void execute_invalidPersonIndexFilteredList_failure() {
EditCommand editCommand = new EditCommand(outOfBoundIndex,
new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build());
- assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void execute_invalidBeginAndEndTimes_throwsAssertionError() {
+ assertThrows(AssertionError.class, () -> {
+ new EditPersonDescriptorBuilder(new PersonBuilder()
+ .withBegin("2200")
+ .withEnd("2100")
+ .build()).build();
+ });
}
@Test
diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
index b17c1f3d5c2..9b8652fa6f9 100644
--- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
@@ -9,7 +9,6 @@
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 org.junit.jupiter.api.Test;
@@ -51,10 +50,6 @@ public void equals() {
// different address -> returns false
editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build();
assertFalse(DESC_AMY.equals(editedAmy));
-
- // different tags -> returns false
- editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_HUSBAND).build();
- assertFalse(DESC_AMY.equals(editedAmy));
}
@Test
@@ -64,8 +59,13 @@ public void toStringMethod() {
+ editPersonDescriptor.getName().orElse(null) + ", phone="
+ editPersonDescriptor.getPhone().orElse(null) + ", email="
+ editPersonDescriptor.getEmail().orElse(null) + ", address="
- + editPersonDescriptor.getAddress().orElse(null) + ", tags="
- + editPersonDescriptor.getTags().orElse(null) + "}";
+ + editPersonDescriptor.getDay().orElse(null) + ", day="
+ + editPersonDescriptor.getBegin().orElse(null) + ", begin="
+ + editPersonDescriptor.getEnd().orElse(null) + ", end="
+ + editPersonDescriptor.getAddress().orElse(null) + ", paid="
+ + editPersonDescriptor.getPaid().orElse(null) + ", payrate="
+ + editPersonDescriptor.getPayRate().orElse(null) + ", editSchedule="
+ + editPersonDescriptor.getEditSchedule() + "}";
assertEquals(expected, editPersonDescriptor.toString());
}
}
diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
index b8b7dbba91a..704a89d6f9e 100644
--- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
@@ -3,7 +3,7 @@
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.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
+import static seedu.address.logic.Messages.MESSAGE_TUTEES_LISTED_OVERVIEW;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.testutil.TypicalPersons.CARL;
import static seedu.address.testutil.TypicalPersons.ELLE;
@@ -19,6 +19,8 @@
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.NameSubjectPredicate;
+import seedu.address.model.person.SubjectContainsKeywordsPredicate;
/**
* Contains integration tests (interaction with the Model) for {@code FindCommand}.
@@ -31,17 +33,21 @@ public class FindCommandTest {
public void equals() {
NameContainsKeywordsPredicate firstPredicate =
new NameContainsKeywordsPredicate(Collections.singletonList("first"));
- NameContainsKeywordsPredicate secondPredicate =
- new NameContainsKeywordsPredicate(Collections.singletonList("second"));
+ SubjectContainsKeywordsPredicate secondPredicate =
+ new SubjectContainsKeywordsPredicate(Collections.singletonList("second"));
+ NameContainsKeywordsPredicate thirdPredicate =
+ new NameContainsKeywordsPredicate(Collections.singletonList("third"));
+ SubjectContainsKeywordsPredicate fourthPredicate =
+ new SubjectContainsKeywordsPredicate(Collections.singletonList("fourth"));
- FindCommand findFirstCommand = new FindCommand(firstPredicate);
- FindCommand findSecondCommand = new FindCommand(secondPredicate);
+ FindCommand findFirstCommand = new FindCommand(firstPredicate, secondPredicate);
+ FindCommand findSecondCommand = new FindCommand(thirdPredicate, fourthPredicate);
// same object -> returns true
assertTrue(findFirstCommand.equals(findFirstCommand));
// same values -> returns true
- FindCommand findFirstCommandCopy = new FindCommand(firstPredicate);
+ FindCommand findFirstCommandCopy = new FindCommand(firstPredicate, secondPredicate);
assertTrue(findFirstCommand.equals(findFirstCommandCopy));
// different types -> returns false
@@ -54,21 +60,12 @@ public void equals() {
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);
+ String expectedMessage = String.format(MESSAGE_TUTEES_LISTED_OVERVIEW, 3);
NameContainsKeywordsPredicate predicate = preparePredicate("Kurz Elle Kunz");
- FindCommand command = new FindCommand(predicate);
+ SubjectContainsKeywordsPredicate subject = prepareSubject("Biology Maths English");
+ FindCommand command = new FindCommand(predicate, subject);
expectedModel.updateFilteredPersonList(predicate);
assertCommandSuccess(command, model, expectedMessage, expectedModel);
assertEquals(Arrays.asList(CARL, ELLE, FIONA), model.getFilteredPersonList());
@@ -77,7 +74,8 @@ public void execute_multipleKeywords_multiplePersonsFound() {
@Test
public void toStringMethod() {
NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Arrays.asList("keyword"));
- FindCommand findCommand = new FindCommand(predicate);
+ SubjectContainsKeywordsPredicate subject = new SubjectContainsKeywordsPredicate(Arrays.asList("subject"));
+ FindCommand findCommand = new FindCommand(predicate, subject);
String expected = FindCommand.class.getCanonicalName() + "{predicate=" + predicate + "}";
assertEquals(expected, findCommand.toString());
}
@@ -88,4 +86,20 @@ public void toStringMethod() {
private NameContainsKeywordsPredicate preparePredicate(String userInput) {
return new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")));
}
+
+ /**
+ * Parses {@code userInput} into a {@code NameContainsKeywordsPredicate}.
+ */
+ private SubjectContainsKeywordsPredicate prepareSubject(String userInput) {
+ return new SubjectContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")));
+ }
+
+ private NameSubjectPredicate prepareNameSubject(String nameInput, String subjectInput) {
+ NameContainsKeywordsPredicate predicate =
+ new NameContainsKeywordsPredicate(Arrays.asList(nameInput.split("\\s+")));
+
+ SubjectContainsKeywordsPredicate subject =
+ new SubjectContainsKeywordsPredicate(Arrays.asList(subjectInput.split("\\s+")));
+ return new NameSubjectPredicate(predicate, subject);
+ }
}
diff --git a/src/test/java/seedu/address/logic/commands/FreeTimeCommandTest.java b/src/test/java/seedu/address/logic/commands/FreeTimeCommandTest.java
new file mode 100644
index 00000000000..6a5ee212422
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/FreeTimeCommandTest.java
@@ -0,0 +1,99 @@
+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.assertNotEquals;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.interval.Duration;
+import seedu.address.model.interval.Interval;
+import seedu.address.model.interval.IntervalBegin;
+import seedu.address.model.interval.IntervalDay;
+import seedu.address.model.interval.IntervalEnd;
+import seedu.address.model.person.Person;
+import seedu.address.testutil.ModelStub;
+import seedu.address.testutil.PersonBuilder;
+
+
+public class FreeTimeCommandTest {
+ @Test
+ public void constructor_nullPerson_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new FreeTimeCommand(null));
+ }
+
+ @Test
+ public void equals() {
+ Interval interval1 = new Interval(new IntervalDay("Thu"), new Duration("120"), new IntervalBegin("0800"),
+ new IntervalEnd("1000"));
+
+ Interval interval2 = new Interval(new IntervalDay("Wed"), new Duration("120"), new IntervalBegin("0800"),
+ new IntervalEnd("1000"));
+
+ FreeTimeCommand freeTimeCommand1 = new FreeTimeCommand(interval1);
+ FreeTimeCommand freeTimeCommand2 = new FreeTimeCommand(interval2);
+
+ //test for same instance
+ assertEquals(freeTimeCommand1, freeTimeCommand1);
+
+ //test for different instances
+ assertNotEquals(freeTimeCommand1, freeTimeCommand2);
+
+ //not equal to null
+ assertNotEquals(freeTimeCommand1, null);
+ }
+
+ @Test
+ public void execute() throws CommandException {
+ List persons = new ArrayList<>();
+ Person validPerson1 = new PersonBuilder()
+ .withDay("Sat")
+ .withBegin("1500")
+ .withEnd("1600")
+ .build();
+ Person validPerson2 = new PersonBuilder()
+ .withDay("Sat")
+ .withBegin("1000")
+ .withEnd("1200")
+ .build();
+ persons.add(validPerson1);
+ persons.add(validPerson2);
+
+ Interval interval = new Interval(new IntervalDay("Sat"), new Duration("120"), new IntervalBegin("0800"),
+ new IntervalEnd("2200"));
+ FreeTimeCommand freeTimeCommand = new FreeTimeCommand(interval);
+ ModelStub modelStub = new ModelStubWithPersonList(persons);
+
+ CommandResult commandResult = freeTimeCommand.execute(modelStub);
+ String listOfFreeTime = "Free from 08:00 - 10:00\n"
+ + "Free from 12:00 - 15:00\n"
+ + "Free from 16:00 - 22:00\n";
+ assertEquals(String.format(FreeTimeCommand.MESSAGE_SUCCESS, listOfFreeTime),
+ commandResult.getFeedbackToUser());
+ }
+
+ /**
+ * A Model stub that contains a single person.
+ */
+ private class ModelStubWithPersonList extends ModelStub {
+ private final List persons;
+
+ ModelStubWithPersonList(List persons) {
+ requireNonNull(persons);
+ this.persons = persons;
+ }
+
+ @Override
+ public List findInterval(Interval interval) {
+ return this.persons.stream().map(person -> person.getLesson().getTimeSlot()).collect(Collectors.toList());
+ }
+ }
+
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/IsPaidCommandTest.java b/src/test/java/seedu/address/logic/commands/IsPaidCommandTest.java
new file mode 100644
index 00000000000..55c1e3551e0
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/IsPaidCommandTest.java
@@ -0,0 +1,94 @@
+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.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.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Person;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for
+ * {@code IsPaidCommand}.
+ */
+public class IsPaidCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void execute_validIndexUnfilteredList_success() {
+ Person personToCheck = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ IsPaidCommand ispaidCommand = new IsPaidCommand(INDEX_FIRST_PERSON);
+
+ String expectedMessage = String.format(IsPaidCommand.MESSAGE_CHECK_PERSON_PAID,
+ personToCheck.getPaid());
+
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ expectedModel.getPersonPaid(personToCheck);
+
+ assertCommandSuccess(ispaidCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexUnfilteredList_throwsCommandException() {
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
+ IsPaidCommand ispaidCommand = new IsPaidCommand(outOfBoundIndex);
+
+ assertCommandFailure(ispaidCommand, model, Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void execute_invalidIndexFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ IsPaidCommand ispaidCommand = new IsPaidCommand(outOfBoundIndex);
+
+ assertCommandFailure(ispaidCommand, model, Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void equals() {
+ IsPaidCommand isPaidFirstCommand = new IsPaidCommand(INDEX_FIRST_PERSON);
+ IsPaidCommand isPaidSecondCommand = new IsPaidCommand(INDEX_SECOND_PERSON);
+
+ // same object -> returns true
+ assertTrue(isPaidFirstCommand.equals(isPaidFirstCommand));
+
+ // same values -> returns true
+ IsPaidCommand isPaidFirstCommandCopy = new IsPaidCommand(INDEX_FIRST_PERSON);
+ assertTrue(isPaidFirstCommand.equals(isPaidFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(isPaidFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(isPaidFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(isPaidFirstCommand.equals(isPaidSecondCommand));
+ }
+
+ @Test
+ public void toStringMethod() {
+ Index targetIndex = Index.fromOneBased(1);
+ IsPaidCommand ispaidCommand = new IsPaidCommand(targetIndex);
+ String expected = IsPaidCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex + "}";
+ assertEquals(expected, ispaidCommand.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ListByDayCommandTest.java b/src/test/java/seedu/address/logic/commands/ListByDayCommandTest.java
new file mode 100644
index 00000000000..ea19f481f80
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ListByDayCommandTest.java
@@ -0,0 +1,74 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static seedu.address.logic.Messages.MESSAGE_TUTEES_LISTED_OVERVIEW;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.ALICE;
+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.Day;
+import seedu.address.model.person.DayPredicate;
+
+class ListByDayCommandTest {
+
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private final Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void execute_validDay_multipleTuteesFound() {
+ String expectedMessage = String.format(MESSAGE_TUTEES_LISTED_OVERVIEW, 1);
+ Day day = new Day("Mon");
+ DayPredicate predicate = new DayPredicate(day);
+ ListByDayCommand command = new ListByDayCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Arrays.asList(ALICE), model.getFilteredPersonList());
+ }
+
+ @Test
+ public void execute_validDay_noTuteesFound() {
+ String expectedMessage = String.format(MESSAGE_TUTEES_LISTED_OVERVIEW, 0);
+ Day day = new Day("Wed");
+ DayPredicate predicate = new DayPredicate(day);
+ ListByDayCommand command = new ListByDayCommand(predicate);
+ expectedModel.updateFilteredPersonList(predicate);
+ assertCommandSuccess(command, model, expectedMessage, expectedModel);
+ assertEquals(Collections.EMPTY_LIST, model.getFilteredPersonList());
+ }
+
+ @Test
+ public void equals() {
+ Day firstDay = new Day("Mon");
+ Day secondDay = new Day("Mon");
+
+ DayPredicate firstPredicate = new DayPredicate(firstDay);
+ DayPredicate secondPredicate = new DayPredicate(secondDay);
+
+ ListByDayCommand listByDayFirstCommand = new ListByDayCommand(firstPredicate);
+ ListByDayCommand listByDaySecondCommand = new ListByDayCommand(secondPredicate);
+
+ // same object -> returns true
+ assertEquals(listByDayFirstCommand, listByDayFirstCommand);
+
+ // same values -> returns true
+ assertEquals(listByDayFirstCommand, listByDaySecondCommand);
+
+ // different types -> returns false
+ assertNotEquals(new ListCommand(), listByDayFirstCommand);
+
+ // null -> returns false
+ assertNotEquals(null, listByDayFirstCommand);
+
+ // different type -> returns false
+ assertNotEquals(3, listByDayFirstCommand);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ListUnPaidCommandTest.java b/src/test/java/seedu/address/logic/commands/ListUnPaidCommandTest.java
new file mode 100644
index 00000000000..82bf391fac6
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ListUnPaidCommandTest.java
@@ -0,0 +1,36 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.person.PaidPredicate;
+
+public class ListUnPaidCommandTest {
+
+ @Test
+ public void equals() {
+ PaidPredicate firstPredicate = new PaidPredicate(false);
+ PaidPredicate secondPredicate = new PaidPredicate(true);
+
+ ListUnPaidCommand listUnPaidFirstCommand = new ListUnPaidCommand(firstPredicate);
+ ListUnPaidCommand listUnPaidSecondCommand = new ListUnPaidCommand(secondPredicate);
+
+ // same object -> returns true
+ assertTrue(listUnPaidFirstCommand.equals(listUnPaidFirstCommand));
+
+ // same values -> returns true
+ ListUnPaidCommand listUnPaidFirstCommandCopy = new ListUnPaidCommand(firstPredicate);
+ assertTrue(listUnPaidFirstCommand.equals(listUnPaidFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(listUnPaidFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(listUnPaidFirstCommand.equals(null));
+
+ // different command -> returns false
+ assertFalse(listUnPaidFirstCommand.equals(listUnPaidSecondCommand));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/PaidCommandTest.java b/src/test/java/seedu/address/logic/commands/PaidCommandTest.java
new file mode 100644
index 00000000000..a1cc46f6ac0
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/PaidCommandTest.java
@@ -0,0 +1,94 @@
+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.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.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Person;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for
+ * {@code PaidCommand}.
+ */
+public class PaidCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void execute_validIndexUnfilteredList_success() {
+ Person personToPaid = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ PaidCommand paidCommand = new PaidCommand(INDEX_FIRST_PERSON);
+
+ String expectedMessage = String.format(PaidCommand.MESSAGE_MARK_PERSON_PAID_SUCCESS,
+ personToPaid.getPaid());
+
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ expectedModel.markPersonPaid(personToPaid);
+
+ assertCommandSuccess(paidCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexUnfilteredList_throwsCommandException() {
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
+ PaidCommand paidCommand = new PaidCommand(outOfBoundIndex);
+
+ assertCommandFailure(paidCommand, model, Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void execute_invalidIndexFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ PaidCommand paidCommand = new PaidCommand(outOfBoundIndex);
+
+ assertCommandFailure(paidCommand, model, Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void equals() {
+ PaidCommand paidFirstCommand = new PaidCommand(INDEX_FIRST_PERSON);
+ PaidCommand paidSecondCommand = new PaidCommand(INDEX_SECOND_PERSON);
+
+ // same object -> returns true
+ assertTrue(paidFirstCommand.equals(paidFirstCommand));
+
+ // same values -> returns true
+ PaidCommand paidFirstCommandCopy = new PaidCommand(INDEX_FIRST_PERSON);
+ assertTrue(paidFirstCommand.equals(paidFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(paidFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(paidFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(paidFirstCommand.equals(paidSecondCommand));
+ }
+
+ @Test
+ public void toStringMethod() {
+ Index targetIndex = Index.fromOneBased(1);
+ PaidCommand paidCommand = new PaidCommand(targetIndex);
+ String expected = PaidCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex + "}";
+ assertEquals(expected, paidCommand.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/RedoCommandTest.java b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java
new file mode 100644
index 00000000000..c87a866c5ef
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java
@@ -0,0 +1,43 @@
+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.ALICE;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+
+public class RedoCommandTest {
+ @Test
+ public void execute_initAddressBook_failure() {
+ Model model = new ModelManager();
+
+ assertCommandFailure(new RedoCommand(), model, Messages.MESSAGE_CANNOT_REDO);
+ }
+
+ @Test
+ public void execute_endVersionAddressBook_failure() {
+ Model model = new ModelManager();
+
+ CommandTestUtil.simulateAddCommand(model, ALICE);
+
+ assertCommandFailure(new RedoCommand(), model, Messages.MESSAGE_CANNOT_REDO);
+ }
+
+ @Test
+ public void execute_multipleVersion_success() {
+ Model model = new ModelManager();
+ Model successModel = new ModelManager();
+ successModel.addPerson(ALICE);
+
+ CommandTestUtil.simulateAddCommand(model, ALICE);
+
+ // Simulate undo command
+ model.undoAddressBook();
+
+ assertCommandSuccess(new RedoCommand(), model, RedoCommand.MESSAGE_SUCCESS, successModel);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/RevenueCommandTest.java b/src/test/java/seedu/address/logic/commands/RevenueCommandTest.java
new file mode 100644
index 00000000000..1cd51d9ba2f
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/RevenueCommandTest.java
@@ -0,0 +1,50 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.FIONA;
+import static seedu.address.testutil.TypicalPersons.HOON;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.testutil.AddressBookBuilder;
+
+
+
+public class RevenueCommandTest {
+
+ @Test
+ public void execute_validRevenue() {
+ // create addressbook with two persons
+ AddressBookBuilder addressbook = new AddressBookBuilder();
+ addressbook.withPerson(HOON);
+ addressbook.withPerson(FIONA);
+
+ // expected values
+ double expectedRevenue = HOON.getMonthlyFee() + FIONA.getMonthlyFee();
+ String formattedTotalRevenue = String.format("%.2f", expectedRevenue);
+ String expectedMessage = RevenueCommand.MESSAGE_SUCCESS + formattedTotalRevenue;
+
+ Command command = new RevenueCommand();
+ Model model = new ModelManager(addressbook.build(), new UserPrefs());
+ assertCommandSuccess(command, model, expectedMessage, model);
+ }
+
+ @Test
+ public void execute_validRevenueEmptyList() {
+ // create addressbook with no persons
+ AddressBookBuilder addressbook = new AddressBookBuilder();
+
+ // expected 0.00
+ double expectedRevenue = 0.0;
+ String formattedTotalRevenue = String.format("%.2f", expectedRevenue);
+ String expectedMessage = RevenueCommand.MESSAGE_SUCCESS + formattedTotalRevenue;
+
+ Command command = new RevenueCommand();
+ Model model = new ModelManager(addressbook.build(), new UserPrefs());
+ assertCommandSuccess(command, model, expectedMessage, model);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/UnPaidAllCommandTest.java b/src/test/java/seedu/address/logic/commands/UnPaidAllCommandTest.java
new file mode 100644
index 00000000000..d273e93886b
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/UnPaidAllCommandTest.java
@@ -0,0 +1,20 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.UnPaidAllCommand.MESSAGE_MARK_ALL_PERSON_UNPAID_SUCCESS;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+
+public class UnPaidAllCommandTest {
+ private Model model = new ModelManager();
+ private Model expectedModel = new ModelManager();
+
+ @Test
+ public void execute_unpaidAll_success() {
+ CommandResult expectedCommandResult = new CommandResult(MESSAGE_MARK_ALL_PERSON_UNPAID_SUCCESS, false, false);
+ assertCommandSuccess(new UnPaidAllCommand(), model, expectedCommandResult, expectedModel);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/UnPaidCommandTest.java b/src/test/java/seedu/address/logic/commands/UnPaidCommandTest.java
new file mode 100644
index 00000000000..ec5d48855fb
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/UnPaidCommandTest.java
@@ -0,0 +1,94 @@
+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.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.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Person;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for
+ * {@code UnpaidCommand}.
+ */
+public class UnPaidCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+
+ @Test
+ public void execute_validIndexUnfilteredList_success() {
+ Person personToUnPaid = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ UnPaidCommand unpaidCommand = new UnPaidCommand(INDEX_FIRST_PERSON);
+
+ String expectedMessage = String.format(UnPaidCommand.MESSAGE_MARK_PERSON_UNPAID_SUCCESS,
+ personToUnPaid.getPaid());
+
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ expectedModel.markPersonUnPaid(personToUnPaid);
+
+ assertCommandSuccess(unpaidCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexUnfilteredList_throwsCommandException() {
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
+ UnPaidCommand unpaidCommand = new UnPaidCommand(outOfBoundIndex);
+
+ assertCommandFailure(unpaidCommand, model, Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void execute_invalidIndexFilteredList_throwsCommandException() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ UnPaidCommand unpaidCommand = new UnPaidCommand(outOfBoundIndex);
+
+ assertCommandFailure(unpaidCommand, model, Messages.MESSAGE_INVALID_TUTEE_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void equals() {
+ UnPaidCommand unpaidFirstCommand = new UnPaidCommand(INDEX_FIRST_PERSON);
+ UnPaidCommand unpaidSecondCommand = new UnPaidCommand(INDEX_SECOND_PERSON);
+
+ // same object -> returns true
+ assertTrue(unpaidFirstCommand.equals(unpaidFirstCommand));
+
+ // same values -> returns true
+ UnPaidCommand unpaidFirstCommandCopy = new UnPaidCommand(INDEX_FIRST_PERSON);
+ assertTrue(unpaidFirstCommand.equals(unpaidFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(unpaidFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(unpaidFirstCommand.equals(null));
+
+ // different person -> returns false
+ assertFalse(unpaidFirstCommand.equals(unpaidSecondCommand));
+ }
+
+ @Test
+ public void toStringMethod() {
+ Index targetIndex = Index.fromOneBased(1);
+ UnPaidCommand unpaidCommand = new UnPaidCommand(targetIndex);
+ String expected = UnPaidCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex + "}";
+ assertEquals(expected, unpaidCommand.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/UndoCommandTest.java b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java
new file mode 100644
index 00000000000..802247161b0
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java
@@ -0,0 +1,38 @@
+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.ALICE;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+
+public class UndoCommandTest {
+ private Model model;
+ private Model successModel;
+
+
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ successModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ }
+ @Test
+ public void execute_noCommandExecuted_failure() {
+ assertCommandFailure(new UndoCommand(), model, Messages.MESSAGE_CANNOT_UNDO);
+ }
+
+ @Test
+ public void execute_oneModifyDataCommandExecuted_success() {
+ // Delete person modifies tutee data
+ CommandTestUtil.simulateDeleteCommand(model, ALICE);
+
+ assertCommandSuccess(new UndoCommand(), model, UndoCommand.MESSAGE_SUCCESS, successModel);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
index 5bc11d3cdaa..c95d8fd4585 100644
--- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
@@ -3,31 +3,46 @@
import static seedu.address.logic.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.BEGIN_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.BEGIN_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.DAY_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.DAY_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.END_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.END_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_BEGIN_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_DAY_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_END_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_PAYRATE_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.INVALID_SUBJECT_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.PAYRATE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.PAYRATE_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.SUBJECT_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_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_FRIEND;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
import static seedu.address.testutil.TypicalPersons.AMY;
@@ -38,11 +53,15 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.AddCommand;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Begin;
+import seedu.address.model.person.Day;
import seedu.address.model.person.Email;
+import seedu.address.model.person.End;
import seedu.address.model.person.Name;
+import seedu.address.model.person.PayRate;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Subject;
import seedu.address.testutil.PersonBuilder;
public class AddCommandParserTest {
@@ -50,25 +69,20 @@ public class AddCommandParserTest {
@Test
public void parse_allFieldsPresent_success() {
- Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build();
+ Person expectedPerson = new PersonBuilder(BOB).build();
// whitespace only preamble
assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson));
+ + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + DAY_DESC_BOB + BEGIN_DESC_BOB
+ + END_DESC_BOB + PAYRATE_DESC_BOB, 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_repeatedNonTagValue_failure() {
+ public void parse_repeatedValue_failure() {
String validExpectedPersonString = NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND;
+ + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + DAY_DESC_BOB + BEGIN_DESC_BOB
+ + END_DESC_BOB + PAYRATE_DESC_BOB;
// multiple names
assertParseFailure(parser, NAME_DESC_AMY + validExpectedPersonString,
@@ -86,11 +100,28 @@ public void parse_repeatedNonTagValue_failure() {
assertParseFailure(parser, ADDRESS_DESC_AMY + validExpectedPersonString,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
+ // multiple subjects
+ assertParseFailure(parser, SUBJECT_DESC_AMY + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_SUBJECT));
+
+ // multiple days
+ assertParseFailure(parser, DAY_DESC_AMY + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_DAY));
+
+ //multiple begins
+ assertParseFailure(parser, BEGIN_DESC_AMY + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_BEGIN));
+
+ //multiple ends
+ assertParseFailure(parser, END_DESC_AMY + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_END));
+
// multiple fields repeated
assertParseFailure(parser,
validExpectedPersonString + PHONE_DESC_AMY + EMAIL_DESC_AMY + NAME_DESC_AMY + ADDRESS_DESC_AMY
- + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_ADDRESS, PREFIX_EMAIL, PREFIX_PHONE));
+ + DAY_DESC_AMY + validExpectedPersonString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_ADDRESS, PREFIX_EMAIL,
+ PREFIX_PHONE, PREFIX_DAY, PREFIX_SUBJECT, PREFIX_BEGIN, PREFIX_END, PREFIX_PAYRATE));
// invalid value followed by valid value
@@ -132,8 +163,10 @@ public void parse_repeatedNonTagValue_failure() {
@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,
+ Person expectedPerson = new PersonBuilder(AMY).build();
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY
+ + ADDRESS_DESC_AMY + SUBJECT_DESC_AMY + DAY_DESC_AMY + BEGIN_DESC_AMY + END_DESC_AMY
+ + PAYRATE_DESC_AMY,
new AddCommand(expectedPerson));
}
@@ -165,32 +198,60 @@ public void parse_compulsoryFieldMissing_failure() {
@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);
+ assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + DAY_DESC_BOB + BEGIN_DESC_BOB + END_DESC_BOB
+ + PAYRATE_DESC_BOB, 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);
+ assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB
+ + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + DAY_DESC_BOB + BEGIN_DESC_BOB + END_DESC_BOB
+ + PAYRATE_DESC_BOB, 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);
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC
+ + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + DAY_DESC_BOB + BEGIN_DESC_BOB + END_DESC_BOB
+ + PAYRATE_DESC_BOB, 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);
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ + INVALID_ADDRESS_DESC + SUBJECT_DESC_BOB + DAY_DESC_BOB + BEGIN_DESC_BOB + END_DESC_BOB
+ + PAYRATE_DESC_BOB, Address.MESSAGE_CONSTRAINTS);
+
+ // invalid subject
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ + ADDRESS_DESC_BOB + INVALID_SUBJECT_DESC + DAY_DESC_BOB + BEGIN_DESC_BOB + END_DESC_BOB
+ + PAYRATE_DESC_BOB, Subject.MESSAGE_CONSTRAINTS);
+
+ // invalid day
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + INVALID_DAY_DESC + BEGIN_DESC_BOB + END_DESC_BOB
+ + PAYRATE_DESC_BOB, Day.MESSAGE_CONSTRAINTS);
+
+ // invalid begin
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + DAY_DESC_BOB + INVALID_BEGIN_DESC + END_DESC_BOB
+ + PAYRATE_DESC_BOB, Begin.MESSAGE_CONSTRAINTS);
+
+ // invalid end
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + DAY_DESC_BOB + BEGIN_DESC_BOB + INVALID_END_DESC
+ + PAYRATE_DESC_BOB, End.MESSAGE_CONSTRAINTS);
+
+ // invalid payrate
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + DAY_DESC_BOB + BEGIN_DESC_BOB + END_DESC_BOB
+ + INVALID_PAYRATE_DESC, PayRate.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);
+ assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ + INVALID_ADDRESS_DESC + SUBJECT_DESC_BOB + DAY_DESC_BOB + BEGIN_DESC_BOB + END_DESC_BOB
+ + PAYRATE_DESC_BOB, 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,
+ + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + DAY_DESC_BOB + BEGIN_DESC_BOB + END_DESC_BOB
+ + PAYRATE_DESC_BOB,
String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
+
}
}
diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
index 5a1ab3dbc0c..70977215b42 100644
--- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
@@ -4,6 +4,8 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
@@ -22,9 +24,12 @@
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.UnPaidAllCommand;
+import seedu.address.logic.commands.UnPaidCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
+import seedu.address.model.person.SubjectContainsKeywordsPredicate;
import seedu.address.testutil.EditPersonDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
import seedu.address.testutil.PersonUtil;
@@ -53,6 +58,13 @@ public void parseCommand_delete() throws Exception {
assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command);
}
+ @Test
+ public void parseCommand_unpaid() throws Exception {
+ UnPaidCommand command = (UnPaidCommand) parser.parseCommand(
+ UnPaidCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased());
+ assertEquals(new UnPaidCommand(INDEX_FIRST_PERSON), command);
+ }
+
@Test
public void parseCommand_edit() throws Exception {
Person person = new PersonBuilder().build();
@@ -70,10 +82,17 @@ public void parseCommand_exit() throws Exception {
@Test
public void parseCommand_find() throws Exception {
- List keywords = Arrays.asList("foo", "bar", "baz");
+ List keywords = Arrays.asList("foo");
+ List subjects = Arrays.asList("Maths");
FindCommand command = (FindCommand) parser.parseCommand(
- FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" ")));
- assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command);
+ FindCommand.COMMAND_WORD + " " + keywords.stream()
+ .map(keyword -> PREFIX_NAME + keyword)
+ .collect(Collectors.joining(" "))
+ + " " + subjects.stream()
+ .map(subject -> PREFIX_SUBJECT + subject)
+ .collect(Collectors.joining(" ")));
+ assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords),
+ new SubjectContainsKeywordsPredicate(subjects)), command);
}
@Test
@@ -82,10 +101,15 @@ public void parseCommand_help() throws Exception {
assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD + " 3") instanceof HelpCommand);
}
+ @Test
+ public void parseCommand_unpaidAll() throws Exception {
+ assertTrue(parser.parseCommand(UnPaidAllCommand.COMMAND_WORD) instanceof UnPaidAllCommand);
+ assertTrue(parser.parseCommand(UnPaidAllCommand.COMMAND_WORD + " 3") instanceof UnPaidAllCommand);
+ }
+
@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
diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
index cc7175172d4..55dcbb1d002 100644
--- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
@@ -8,24 +8,23 @@
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_PAYRATE_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.PAYRATE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.PAYRATE_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.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_EMAIL_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PAYRATE_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.parser.CliSyntax.PREFIX_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-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.TypicalIndexes.INDEX_FIRST_PERSON;
@@ -41,14 +40,12 @@
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
+import seedu.address.model.person.PayRate;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
import seedu.address.testutil.EditPersonDescriptorBuilder;
public class EditCommandParserTest {
- private static final String TAG_EMPTY = " " + PREFIX_TAG;
-
private static final String MESSAGE_INVALID_FORMAT =
String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE);
@@ -87,17 +84,11 @@ public void parse_invalidValue_failure() {
assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone
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_PAYRATE_DESC, PayRate.MESSAGE_CONSTRAINTS); // invalid payrate
// invalid phone followed by valid email
assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS);
- // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited,
- // parsing it together with a valid tag results in error
- assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS);
- assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
- assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
-
// multiple invalid values, but only the first invalid value is captured
assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY,
Name.MESSAGE_CONSTRAINTS);
@@ -106,12 +97,12 @@ public void parse_invalidValue_failure() {
@Test
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;
+ String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + EMAIL_DESC_AMY
+ + ADDRESS_DESC_AMY + NAME_DESC_AMY + PAYRATE_DESC_AMY;
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();
+ .withPayRate(VALID_PAYRATE_AMY).build();
EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
@@ -156,11 +147,12 @@ public void parse_oneFieldSpecified_success() {
expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
- // tags
- userInput = targetIndex.getOneBased() + TAG_DESC_FRIEND;
- descriptor = new EditPersonDescriptorBuilder().withTags(VALID_TAG_FRIEND).build();
+ // payrate
+ userInput = targetIndex.getOneBased() + PAYRATE_DESC_AMY;
+ descriptor = new EditPersonDescriptorBuilder().withPayRate(VALID_PAYRATE_AMY).build();
expectedCommand = new EditCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
+
}
@Test
@@ -181,8 +173,8 @@ public void parse_multipleRepeatedFields_failure() {
// mulltiple valid fields repeated
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_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + PHONE_DESC_BOB
+ + ADDRESS_DESC_BOB + EMAIL_DESC_BOB;
assertParseFailure(parser, userInput,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
@@ -193,16 +185,10 @@ public void parse_multipleRepeatedFields_failure() {
assertParseFailure(parser, userInput,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
- }
- @Test
- public void parse_resetTags_success() {
- Index targetIndex = INDEX_THIRD_PERSON;
- String userInput = targetIndex.getOneBased() + TAG_EMPTY;
-
- EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build();
- EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
-
- assertParseSuccess(parser, userInput, expectedCommand);
+ // multiple PayRate fields repeated
+ userInput = targetIndex.getOneBased() + PAYRATE_DESC_BOB + PAYRATE_DESC_AMY;
+ assertParseFailure(parser, userInput,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PAYRATE));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
index d92e64d12f9..1655b23ef39 100644
--- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
@@ -3,13 +3,16 @@
import static seedu.address.logic.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.Assert.assertThrows;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.SubjectContainsKeywordsPredicate;
public class FindCommandParserTest {
@@ -20,15 +23,37 @@ public void parse_emptyArg_throwsParseException() {
assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
+ @Test
+ public void parse_multipleWords_throwsParseException() {
+
+ assertThrows(ParseException.class, () -> parser.parse("n/Alex Yeoh sb/Math"));
+
+ assertThrows(ParseException.class, () -> parser.parse("sb/Math Chemistry"));
+
+ assertThrows(ParseException.class, () -> parser.parse("n/Alex Yeoh sb/Math"));
+
+ assertThrows(ParseException.class, () -> parser.parse("n/Alex sb/Math Chemistry"));
+ }
+
+ @Test
+ public void parse_validOneWord_success() throws ParseException {
+ parser.parse(" n/Alex");
+
+ parser.parse(" sb/Math");
+ }
+
@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);
+ new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Bob")),
+ new SubjectContainsKeywordsPredicate(Arrays.asList("Biology")));
+
+ assertParseSuccess(parser, " n/Bob sb/Biology", expectedFindCommand);
// multiple whitespaces between keywords
- assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand);
+ assertParseSuccess(parser, " \n n/Bob \t \n sb/Biology \t",
+ expectedFindCommand);
}
}
diff --git a/src/test/java/seedu/address/logic/parser/FreeTimeCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FreeTimeCommandParserTest.java
new file mode 100644
index 00000000000..2611b3a97b1
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/FreeTimeCommandParserTest.java
@@ -0,0 +1,122 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.INTERVAL_BEGIN_DESC_ONE;
+import static seedu.address.logic.commands.CommandTestUtil.INTERVAL_DAY_DESC_ONE;
+import static seedu.address.logic.commands.CommandTestUtil.INTERVAL_DURATION_DESC_ONE;
+import static seedu.address.logic.commands.CommandTestUtil.INTERVAL_END_DESC_ONE;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_INTERVAL_BEGIN_DESC_ONE;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_INTERVAL_DAY_DESC_ONE;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_INTERVAL_DURATION_DESC_ONE;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_INTERVAL_END_DESC_ONE;
+import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_INTERVAL_BEGIN;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_INTERVAL_DAY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_INTERVAL_DURATION;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_INTERVAL_END;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_INTERVAL_ONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END;
+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.Messages;
+import seedu.address.logic.commands.FreeTimeCommand;
+import seedu.address.model.interval.Duration;
+import seedu.address.model.interval.IntervalBegin;
+import seedu.address.model.interval.IntervalDay;
+import seedu.address.model.interval.IntervalEnd;
+
+public class FreeTimeCommandParserTest {
+
+ private FreeTimeCommandParser parser = new FreeTimeCommandParser();
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+ // whitespace only preamble
+ assertParseSuccess(parser, PREAMBLE_WHITESPACE + INTERVAL_DAY_DESC_ONE + INTERVAL_DURATION_DESC_ONE
+ + INTERVAL_BEGIN_DESC_ONE + INTERVAL_END_DESC_ONE, new FreeTimeCommand(VALID_INTERVAL_ONE));
+ }
+
+ @Test
+ public void parse_repeatedValue_failure() {
+ String validExpectedInterval = INTERVAL_DAY_DESC_ONE + INTERVAL_DURATION_DESC_ONE
+ + INTERVAL_BEGIN_DESC_ONE + INTERVAL_END_DESC_ONE;
+
+ // multiple days
+ assertParseFailure(parser, INTERVAL_DAY_DESC_ONE + validExpectedInterval,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_DAY));
+
+ // multiple durations
+ assertParseFailure(parser, INTERVAL_DURATION_DESC_ONE + validExpectedInterval,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_DURATION));
+
+ //multiple begin
+ assertParseFailure(parser, INTERVAL_BEGIN_DESC_ONE + validExpectedInterval,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_BEGIN));
+
+ //multiple end
+ assertParseFailure(parser, INTERVAL_END_DESC_ONE + validExpectedInterval,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_END));
+
+ // invalid value followed by valid value
+ assertParseFailure(parser, INVALID_INTERVAL_DAY_DESC_ONE + validExpectedInterval,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_DAY));
+
+ assertParseFailure(parser, INVALID_INTERVAL_BEGIN_DESC_ONE + validExpectedInterval,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_BEGIN));
+
+ assertParseFailure(parser, INVALID_INTERVAL_END_DESC_ONE + validExpectedInterval,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_END));
+
+ assertParseFailure(parser, INVALID_INTERVAL_DURATION_DESC_ONE + validExpectedInterval,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_DURATION));
+
+ }
+
+ @Test
+ public void parse_compulsoryFieldMissing_failure() {
+ String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, FreeTimeCommand.MESSAGE_USAGE);
+
+ // missing day prefix
+ assertParseFailure(parser, VALID_INTERVAL_DAY + INTERVAL_DURATION_DESC_ONE + INTERVAL_BEGIN_DESC_ONE
+ + INTERVAL_END_DESC_ONE, expectedMessage);
+
+ // missing DURATION prefix
+ assertParseFailure(parser, INTERVAL_DAY_DESC_ONE + VALID_INTERVAL_DURATION + INTERVAL_BEGIN_DESC_ONE
+ + INTERVAL_END_DESC_ONE, expectedMessage);
+
+ // missing BEGIN prefix
+ assertParseFailure(parser, INTERVAL_DAY_DESC_ONE + INTERVAL_DURATION_DESC_ONE + VALID_INTERVAL_BEGIN
+ + INTERVAL_END_DESC_ONE, expectedMessage);
+
+ // missing END prefix
+ assertParseFailure(parser, INTERVAL_DAY_DESC_ONE + INTERVAL_DURATION_DESC_ONE + INTERVAL_BEGIN_DESC_ONE
+ + VALID_INTERVAL_END, expectedMessage);
+
+ }
+
+ @Test
+ public void parse_invalidValue_failure() {
+ //invalid day
+ assertParseFailure(parser, INVALID_INTERVAL_DAY_DESC_ONE + INTERVAL_DURATION_DESC_ONE
+ + INTERVAL_BEGIN_DESC_ONE + INTERVAL_END_DESC_ONE, IntervalDay.MESSAGE_CONSTRAINTS);
+
+ //invalid duration
+ assertParseFailure(parser, INTERVAL_DAY_DESC_ONE + INVALID_INTERVAL_DURATION_DESC_ONE
+ + INTERVAL_BEGIN_DESC_ONE + INTERVAL_END_DESC_ONE, Duration.MESSAGE_CONSTRAINTS);
+
+ //invalid begin
+ assertParseFailure(parser, INTERVAL_DAY_DESC_ONE + INTERVAL_DURATION_DESC_ONE
+ + INVALID_INTERVAL_BEGIN_DESC_ONE + INTERVAL_END_DESC_ONE, IntervalBegin.MESSAGE_CONSTRAINTS);
+
+ //invalid end
+ assertParseFailure(parser, INTERVAL_DAY_DESC_ONE + INTERVAL_DURATION_DESC_ONE
+ + INTERVAL_BEGIN_DESC_ONE + INVALID_INTERVAL_END_DESC_ONE, IntervalEnd.MESSAGE_CONSTRAINTS);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/IsPaidCommandParserTest.java b/src/test/java/seedu/address/logic/parser/IsPaidCommandParserTest.java
new file mode 100644
index 00000000000..d5117fb963c
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/IsPaidCommandParserTest.java
@@ -0,0 +1,25 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.IsPaidCommand;
+
+public class IsPaidCommandParserTest {
+
+ private IsPaidCommandParser parser = new IsPaidCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsUnpaidCommand() {
+ assertParseSuccess(parser, "1", new IsPaidCommand(INDEX_FIRST_PERSON));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "sdfas", String.format(MESSAGE_INVALID_COMMAND_FORMAT, IsPaidCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/PaidCommandParserTest.java b/src/test/java/seedu/address/logic/parser/PaidCommandParserTest.java
new file mode 100644
index 00000000000..38ed4bf70c8
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/PaidCommandParserTest.java
@@ -0,0 +1,25 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.PaidCommand;
+
+public class PaidCommandParserTest {
+
+ private PaidCommandParser parser = new PaidCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsUnpaidCommand() {
+ assertParseSuccess(parser, "1", new PaidCommand(INDEX_FIRST_PERSON));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "dfass", String.format(MESSAGE_INVALID_COMMAND_FORMAT, PaidCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
index 4256788b1a7..3b1897add7e 100644
--- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
+++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
@@ -1,39 +1,34 @@
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.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
import org.junit.jupiter.api.Test;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
+import seedu.address.model.person.PayRate;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
public class ParserUtilTest {
private static final String INVALID_NAME = "R@chel";
private static final String INVALID_PHONE = "+651234";
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_NEGATIVE_PAYRATE = "-0";
+
+ private static final String INVALID_STRING_PAYRATE = "abcabc";
private static final String VALID_NAME = "Rachel Walker";
private static final String VALID_PHONE = "123456";
private static final String VALID_ADDRESS = "123 Main Street #0505";
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_PAYRATE = "20.00";
private static final String WHITESPACE = " \t\r\n";
@Test
@@ -149,48 +144,30 @@ public void parseEmail_validValueWithWhitespace_returnsTrimmedEmail() throws Exc
}
@Test
- public void parseTag_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null));
- }
-
- @Test
- public void parseTag_invalidValue_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseTag(INVALID_TAG));
+ public void parsePayRate_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> ParserUtil.parsePayRate((String) null));
}
@Test
- public void parseTag_validValueWithoutWhitespace_returnsTag() throws Exception {
- Tag expectedTag = new Tag(VALID_TAG_1);
- assertEquals(expectedTag, ParserUtil.parseTag(VALID_TAG_1));
+ public void parsePayRate_invalidNegativeValue_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parsePayRate(INVALID_NEGATIVE_PAYRATE));
}
@Test
- public void parseTag_validValueWithWhitespace_returnsTrimmedTag() throws Exception {
- String tagWithWhitespace = WHITESPACE + VALID_TAG_1 + WHITESPACE;
- Tag expectedTag = new Tag(VALID_TAG_1);
- assertEquals(expectedTag, ParserUtil.parseTag(tagWithWhitespace));
+ public void parsePayRate_invalidStringValue_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parsePayRate(INVALID_STRING_PAYRATE));
}
@Test
- public void parseTags_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> ParserUtil.parseTags(null));
+ public void parsePayRate_validValueWithoutWhitespace_returnsPayRate() throws Exception {
+ PayRate expectedPayRate = new PayRate(VALID_PAYRATE);
+ assertEquals(expectedPayRate, ParserUtil.parsePayRate(VALID_PAYRATE));
}
@Test
- public void parseTags_collectionWithInvalidTags_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, INVALID_TAG)));
- }
-
- @Test
- public void parseTags_emptyCollection_returnsEmptySet() throws Exception {
- assertTrue(ParserUtil.parseTags(Collections.emptyList()).isEmpty());
- }
-
- @Test
- public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception {
- Set actualTagSet = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2));
- Set expectedTagSet = new HashSet(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2)));
-
- assertEquals(expectedTagSet, actualTagSet);
+ public void parsePayRate_validValueWithWhitespace_returnsPayRate() throws Exception {
+ String payRateWithWhitespace = WHITESPACE + VALID_PAYRATE + WHITESPACE;
+ PayRate expectedPayRate = new PayRate(VALID_PAYRATE);
+ assertEquals(expectedPayRate, ParserUtil.parsePayRate(payRateWithWhitespace));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/UnPaidCommandParserTest.java b/src/test/java/seedu/address/logic/parser/UnPaidCommandParserTest.java
new file mode 100644
index 00000000000..3b2eb9568c9
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/UnPaidCommandParserTest.java
@@ -0,0 +1,25 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.UnPaidCommand;
+
+public class UnPaidCommandParserTest {
+
+ private UnPaidCommandParser parser = new UnPaidCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsUnpaidCommand() {
+ assertParseSuccess(parser, "1", new UnPaidCommand(INDEX_FIRST_PERSON));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, "dfass", String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnPaidCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java
index 68c8c5ba4d5..aa3206c5fac 100644
--- a/src/test/java/seedu/address/model/AddressBookTest.java
+++ b/src/test/java/seedu/address/model/AddressBookTest.java
@@ -4,11 +4,11 @@
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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -18,6 +18,11 @@
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
+import seedu.address.model.interval.Duration;
+import seedu.address.model.interval.Interval;
+import seedu.address.model.interval.IntervalBegin;
+import seedu.address.model.interval.IntervalDay;
+import seedu.address.model.interval.IntervalEnd;
import seedu.address.model.person.Person;
import seedu.address.model.person.exceptions.DuplicatePersonException;
import seedu.address.testutil.PersonBuilder;
@@ -46,8 +51,7 @@ public void resetData_withValidReadOnlyAddressBook_replacesData() {
@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();
+ Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build();
List newPersons = Arrays.asList(ALICE, editedAlice);
AddressBookStub newData = new AddressBookStub(newPersons);
@@ -70,11 +74,27 @@ public void hasPerson_personInAddressBook_returnsTrue() {
assertTrue(addressBook.hasPerson(ALICE));
}
+ @Test
+ public void findInterval() {
+ addressBook.addPerson(ALICE);
+ Interval interval = new Interval(new IntervalDay("Mon"), new Duration("60"), new IntervalBegin("0600"),
+ new IntervalEnd("2200"));
+ List result = addressBook.findInterval(interval);
+ List expected = new ArrayList<>();
+ expected.add("08:00 - 10:00");
+ assertEquals(expected, result);
+ }
+
+ @Test void setPaidTest() {
+ addressBook.addPerson(ALICE);
+ addressBook.setPaid(ALICE);
+ assertEquals(true, addressBook.getPaid(ALICE));
+ }
+
@Test
public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() {
addressBook.addPerson(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
- .build();
+ Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build();
assertTrue(addressBook.hasPerson(editedAlice));
}
diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java
index 2cf1418d116..ce5a068f464 100644
--- a/src/test/java/seedu/address/model/ModelManagerTest.java
+++ b/src/test/java/seedu/address/model/ModelManagerTest.java
@@ -2,6 +2,7 @@
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.model.Model.PREDICATE_SHOW_ALL_PERSONS;
import static seedu.address.testutil.Assert.assertThrows;
@@ -26,7 +27,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 VersionedAddressBook(), new VersionedAddressBook(modelManager.getAddressBook()));
}
@Test
@@ -93,6 +94,57 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException
assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0));
}
+ @Test
+ public void canUndoAddressBook_initState_returnsFalse() {
+ assertFalse(modelManager.canUndoAddressBook());
+ }
+
+ @Test
+ public void canUndoAddressBook_atNonInitState_returnsTrue() {
+ // Simulate an add command
+ modelManager.purgeAddressBook();
+ modelManager.addPerson(ALICE);
+ modelManager.commitAddressBook();
+
+ assertTrue(modelManager.canUndoAddressBook());
+ }
+
+ @Test
+ public void canRedoAddressBook_initState_returnsFalse() {
+ assertFalse(modelManager.canRedoAddressBook());
+ }
+
+ @Test
+ public void canRedoAddressBook_atUndoVersion_returnsTrue() {
+ // Simulate an add command
+ modelManager.purgeAddressBook();
+ modelManager.addPerson(ALICE);
+ modelManager.commitAddressBook();
+
+ // Simulate undo command
+ modelManager.undoAddressBook();
+
+ assertTrue(modelManager.canRedoAddressBook());
+
+ // Simulate redo command
+ modelManager.redoAddressBook();
+
+ // Simulate delete command
+ modelManager.purgeAddressBook();
+ modelManager.deletePerson(ALICE);
+ modelManager.commitAddressBook();
+
+ modelManager.undoAddressBook();
+ assertTrue(modelManager.hasPerson(ALICE));
+ }
+
+ @Test
+ public void canMarkPersonPaid_canGetPersonPaid_returnsTrue() {
+ modelManager.addPerson(ALICE);
+ modelManager.markPersonPaid(ALICE);
+ assertTrue(modelManager.getPersonPaid(ALICE));
+ }
+
@Test
public void equals() {
AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build();
@@ -102,24 +154,24 @@ public void equals() {
// same values -> returns true
modelManager = new ModelManager(addressBook, userPrefs);
ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs);
- assertTrue(modelManager.equals(modelManagerCopy));
+ assertEquals(modelManager, modelManagerCopy);
// same object -> returns true
- assertTrue(modelManager.equals(modelManager));
+ assertEquals(modelManager, modelManager);
// null -> returns false
- assertFalse(modelManager.equals(null));
+ assertNotEquals(null, modelManager);
// different types -> returns false
- assertFalse(modelManager.equals(5));
+ assertNotEquals(5, modelManager);
// different addressBook -> returns false
- assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs)));
+ assertNotEquals(modelManager, new ModelManager(differentAddressBook, 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)));
+ assertNotEquals(modelManager, new ModelManager(addressBook, userPrefs));
// resets modelManager to initial state for upcoming tests
modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
@@ -127,6 +179,6 @@ public void equals() {
// different userPrefs -> returns false
UserPrefs differentUserPrefs = new UserPrefs();
differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath"));
- assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs)));
+ assertNotEquals(modelManager, new ModelManager(addressBook, differentUserPrefs));
}
}
diff --git a/src/test/java/seedu/address/model/VersionedAddressBookTest.java b/src/test/java/seedu/address/model/VersionedAddressBookTest.java
new file mode 100644
index 00000000000..99d8af373f6
--- /dev/null
+++ b/src/test/java/seedu/address/model/VersionedAddressBookTest.java
@@ -0,0 +1,205 @@
+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.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.BOB;
+import static seedu.address.testutil.TypicalPersons.getTypicalVersionedAddressBook;
+
+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 VersionedAddressBookTest {
+
+ private final VersionedAddressBook oneVersionVersionedAddressBook = new VersionedAddressBook();
+ private final VersionedAddressBook twoVersionVersionedAddressBook = new VersionedAddressBook();
+
+ public void simulateAddCommand(Person person, VersionedAddressBook addressBook) {
+ // purge addressbook
+ addressBook.purge();
+
+ // add person
+ addressBook.addPerson(person);
+
+ // commit addressbook
+ addressBook.commit();
+ }
+
+ @Test
+ public void constructor() {
+ assertEquals(Collections.emptyList(), oneVersionVersionedAddressBook.getPersonList());
+ }
+
+ @Test
+ public void resetData_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () ->
+ oneVersionVersionedAddressBook.resetData(null));
+ }
+
+ @Test
+ public void undo_invalid_throwsIndexOutOfBoundsException() {
+ assertThrows(IndexOutOfBoundsException.class, oneVersionVersionedAddressBook::undo);
+ }
+
+ @Test
+ public void redo_invalid_throwsIndexOutOfBoundsException() {
+ assertThrows(IndexOutOfBoundsException.class, oneVersionVersionedAddressBook::redo);
+ }
+
+ @Test
+ public void canRedo_detects_invalidRedo() {
+ assertFalse(oneVersionVersionedAddressBook.canRedo());
+ }
+
+ @Test
+ public void canUndo_detects_invalidUndo() {
+ assertFalse(oneVersionVersionedAddressBook.canUndo());
+ }
+
+ @Test
+ public void commit_newVersionedAddressBook_updatesPointer() {
+ assertFalse(twoVersionVersionedAddressBook.hasPerson(ALICE));
+
+ simulateAddCommand(ALICE, twoVersionVersionedAddressBook);
+
+ assertTrue(twoVersionVersionedAddressBook.hasPerson(ALICE));
+ }
+
+ @Test
+ public void canRedo_detects_validUndo() {
+ simulateAddCommand(ALICE, twoVersionVersionedAddressBook);
+
+ // Can undo non-empty history and pointer at the end -> true
+ assertTrue(twoVersionVersionedAddressBook.canUndo());
+ }
+
+ @Test
+ public void canUndo_detects_validRedo() {
+ simulateAddCommand(ALICE, twoVersionVersionedAddressBook);
+ assertTrue(twoVersionVersionedAddressBook.hasPerson(ALICE));
+
+ // Undo add command
+ twoVersionVersionedAddressBook.undo();
+ assertFalse(twoVersionVersionedAddressBook.hasPerson(ALICE));
+
+ // Can redo after undo command -> true
+ assertTrue(twoVersionVersionedAddressBook.canRedo());
+ }
+
+ @Test
+ public void purge_redundantDataAbsent_noDataRemoved() {
+ assertFalse(twoVersionVersionedAddressBook.hasPerson(ALICE));
+
+ twoVersionVersionedAddressBook.purge();
+
+ assertEquals(twoVersionVersionedAddressBook, new AddressBook());
+ }
+
+ @Test
+ public void purge_redundantDataPresent_removesRedundantData() {
+ assertFalse(twoVersionVersionedAddressBook.hasPerson(BOB));
+
+ simulateAddCommand(BOB, twoVersionVersionedAddressBook);
+ twoVersionVersionedAddressBook.undo();
+ simulateAddCommand(BOB, twoVersionVersionedAddressBook);
+
+ assertFalse(twoVersionVersionedAddressBook.canRedo());
+ }
+
+ @Test
+ public void resetData_withValidReadOnlyAddressBook_replacesData() {
+ AddressBook newData = getTypicalVersionedAddressBook();
+ oneVersionVersionedAddressBook.resetData(newData);
+
+ // commit new version into history
+ oneVersionVersionedAddressBook.commit();
+
+ assertEquals(newData, oneVersionVersionedAddressBook);
+ }
+
+ @Test
+ public void resetData_withDuplicatePersons_throwsDuplicatePersonException() {
+ // Two persons with the same identity fields
+ Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build();
+ List newPersons = Arrays.asList(ALICE, editedAlice);
+ VersionedAddressBookStub newData = new VersionedAddressBookStub(newPersons);
+
+ assertThrows(DuplicatePersonException.class, () ->
+ oneVersionVersionedAddressBook.resetData(newData));
+ }
+
+ @Test
+ public void hasPerson_nullPerson_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () ->
+ oneVersionVersionedAddressBook.hasPerson(null));
+ }
+
+ @Test
+ public void hasPerson_personNotInAddressBook_returnsFalse() {
+ assertFalse(oneVersionVersionedAddressBook.hasPerson(ALICE));
+ }
+
+ @Test
+ public void hasPerson_personInCurrentAddressBook_returnsTrue() {
+ oneVersionVersionedAddressBook.addPerson(ALICE);
+ assertTrue(oneVersionVersionedAddressBook.hasPerson(ALICE));
+ }
+
+ @Test
+ public void hasPerson_personWithSameIdentityFieldsInCurrentAddressBook_returnsTrue() {
+ simulateAddCommand(ALICE, oneVersionVersionedAddressBook);
+
+ Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build();
+ assertTrue(oneVersionVersionedAddressBook.hasPerson(editedAlice));
+ }
+
+ @Test
+ public void removePerson_removesPersonInAddressBook() {
+ simulateAddCommand(ALICE, oneVersionVersionedAddressBook);
+ oneVersionVersionedAddressBook.removePerson(ALICE);
+ assertFalse(oneVersionVersionedAddressBook.hasPerson(ALICE));
+ }
+
+ @Test
+ public void getPersonList_modifyList_throwsUnsupportedOperationException() {
+ assertThrows(UnsupportedOperationException.class, () ->
+ oneVersionVersionedAddressBook.getPersonList().remove(0));
+ }
+
+ @Test
+ public void toStringMethod() {
+ String expected = VersionedAddressBook.class.getCanonicalName()
+ + "{persons=" + oneVersionVersionedAddressBook.getPersonList() + "}";
+ assertEquals(expected, oneVersionVersionedAddressBook.toString());
+ }
+
+ /**
+ * A stub ReadOnlyAddressBook whose persons list can violate interface constraints.
+ */
+ private static class VersionedAddressBookStub implements ReadOnlyAddressBook {
+ private final ObservableList persons = FXCollections.observableArrayList();
+
+ VersionedAddressBookStub(Collection persons) {
+ this.persons.setAll(persons);
+ }
+
+ @Override
+ public ObservableList getPersonList() {
+ return persons;
+ }
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/interval/DurationTest.java b/src/test/java/seedu/address/model/interval/DurationTest.java
new file mode 100644
index 00000000000..9f3b35db201
--- /dev/null
+++ b/src/test/java/seedu/address/model/interval/DurationTest.java
@@ -0,0 +1,63 @@
+package seedu.address.model.interval;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class DurationTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Duration(null));
+ }
+
+ @Test
+ public void constructor_invalidDuration_throwsIllegalArgumentException() {
+ String invalidDuration = "2.5";
+ assertThrows(IllegalArgumentException.class, () -> new Duration(invalidDuration));
+ }
+
+ @Test
+ public void isValidDuration() {
+
+ // invalid duration
+ assertFalse(Duration.isValidDuration("")); // empty string
+ assertFalse(Duration.isValidDuration("!")); //special character
+ assertFalse(Duration.isValidDuration("0.5")); // decimal
+ assertFalse(Duration.isValidDuration("0")); //zero
+ assertFalse(Duration.isValidDuration("-1")); //negative number
+
+ // valid durations
+ assertTrue(Duration.isValidDuration("60"));
+ assertTrue(Duration.isValidDuration("30"));
+ assertTrue(Duration.isValidDuration("45"));
+ }
+
+ @Test
+ public void equals() {
+ Duration validDuration = new Duration("60");
+
+ // same values -> returns true
+ assertTrue(validDuration.equals(new Duration("60")));
+
+ // same object -> returns true
+ assertTrue(validDuration.equals(validDuration));
+
+ // null -> returns false
+ assertFalse(validDuration.equals(null));
+
+ // different types -> returns false
+ assertFalse(validDuration.equals(60));
+
+ // different values -> returns false
+ assertFalse(validDuration.equals(new Duration("30")));
+ }
+
+ @Test
+ public void toStringMethod() {
+ Duration validDuration = new Duration("60");
+ assertTrue(validDuration.toString() == "60");
+ }
+}
diff --git a/src/test/java/seedu/address/model/interval/IntervalBeginTest.java b/src/test/java/seedu/address/model/interval/IntervalBeginTest.java
new file mode 100644
index 00000000000..2c80dd47401
--- /dev/null
+++ b/src/test/java/seedu/address/model/interval/IntervalBeginTest.java
@@ -0,0 +1,61 @@
+package seedu.address.model.interval;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class IntervalBeginTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new IntervalBegin(null));
+ }
+
+ @Test
+ public void constructor_invalidIntervalBegin_throwsIllegalArgumentException() {
+ String invalidIntervalBegin = "9999";
+ assertThrows(IllegalArgumentException.class, () -> new IntervalBegin(invalidIntervalBegin));
+ }
+
+ @Test
+ public void isValidIntervalBegin() {
+ // null address
+ assertThrows(NullPointerException.class, () -> IntervalBegin.isValidBegin(null));
+
+ // invalid IntervalBegin
+ assertFalse(IntervalBegin.isValidBegin("9999")); // invalid
+ assertFalse(IntervalBegin.isValidBegin("")); // empty
+
+ // valid addresses
+ assertTrue(IntervalBegin.isValidBegin("1800"));
+ assertTrue(IntervalBegin.isValidBegin("0000"));
+ }
+
+ @Test
+ public void equals() {
+ IntervalBegin intervalBegin = new IntervalBegin("1800");
+
+ // same values -> returns true
+ assertTrue(intervalBegin.equals(new IntervalBegin("1800")));
+
+ // same object -> returns true
+ assertTrue(intervalBegin.equals(intervalBegin));
+
+ // null -> returns false
+ assertFalse(intervalBegin.equals(null));
+
+ // different types -> returns false
+ assertFalse(intervalBegin.equals(1800));
+
+ // different values -> returns false
+ assertFalse(intervalBegin.equals(new IntervalBegin("1900")));
+ }
+
+ @Test
+ public void toStringMethod() {
+ IntervalBegin intervalBegin = new IntervalBegin("2100");
+ assertTrue(intervalBegin.toString() == "2100");
+ }
+}
diff --git a/src/test/java/seedu/address/model/interval/IntervalDayTest.java b/src/test/java/seedu/address/model/interval/IntervalDayTest.java
new file mode 100644
index 00000000000..ee7a9e70194
--- /dev/null
+++ b/src/test/java/seedu/address/model/interval/IntervalDayTest.java
@@ -0,0 +1,84 @@
+package seedu.address.model.interval;
+
+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.time.DayOfWeek;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.person.Day;
+
+public class IntervalDayTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new IntervalDay(null));
+ }
+
+ @Test
+ public void constructor_invalidAddress_throwsIllegalArgumentException() {
+ String invalidDay = "Christmas";
+ assertThrows(IllegalArgumentException.class, () -> new IntervalDay(invalidDay));
+ }
+
+ @Test
+ public void isValidIntervalDay() {
+ // null address
+ assertThrows(NullPointerException.class, () -> IntervalDay.isValidDay(null));
+
+ // invalid IntervalDay
+ assertFalse(IntervalDay.isValidDay("")); // empty string
+ assertFalse(IntervalDay.isValidDay("tues")); // not 3 letter word or full day
+
+ // valid addresses
+ assertTrue(IntervalDay.isValidDay("Mon"));
+ assertTrue(IntervalDay.isValidDay("Tuesday"));
+ assertTrue(IntervalDay.isValidDay("wEd"));
+ assertTrue(IntervalDay.isValidDay("SUNDAY"));
+ }
+
+ @Test
+ public void equals() {
+ IntervalDay intervalDay = new IntervalDay("Mon");
+
+ // same values -> returns true
+ assertTrue(intervalDay.equals(new IntervalDay("Monday")));
+
+ // same object -> returns true
+ assertTrue(intervalDay.equals(intervalDay));
+
+ // null -> returns false
+ assertFalse(intervalDay.equals(null));
+
+ // different values -> returns false
+ assertFalse(intervalDay.equals(new IntervalDay("Tuesday")));
+ }
+
+ @Test
+ public void parseDay() {
+ IntervalDay intervalDay = new IntervalDay("Mon");
+ IntervalDay intervalDay2 = new IntervalDay("Fri");
+
+ assertTrue(intervalDay.parseDay("mon") == "Mon");
+ assertTrue(intervalDay2.parseDay("friday") == "Fri");
+ }
+
+ @Test
+ public void parse() {
+ assertTrue(Day.parse("mon").equals(DayOfWeek.MONDAY));
+ assertTrue(Day.parse("friday").equals(DayOfWeek.FRIDAY));
+ }
+
+ @Test
+ public void toStringMethod() {
+ IntervalDay intervalDay = new IntervalDay("mon");
+ IntervalDay intervalDay2 = new IntervalDay("FRIDAY");
+
+ assertTrue(intervalDay.toString().equals("Mon"));
+ assertTrue(intervalDay2.toString().equals("Fri"));
+ }
+
+
+}
diff --git a/src/test/java/seedu/address/model/interval/IntervalEndTest.java b/src/test/java/seedu/address/model/interval/IntervalEndTest.java
new file mode 100644
index 00000000000..d31b7b95ffe
--- /dev/null
+++ b/src/test/java/seedu/address/model/interval/IntervalEndTest.java
@@ -0,0 +1,61 @@
+package seedu.address.model.interval;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class IntervalEndTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new IntervalBegin(null));
+ }
+
+ @Test
+ public void constructor_invalidIntervalEnd_throwsIllegalArgumentException() {
+ String invalidIntervalEnd = "9999";
+ assertThrows(IllegalArgumentException.class, () -> new IntervalBegin(invalidIntervalEnd));
+ }
+
+ @Test
+ public void isValidIntervalEnd() {
+ // null address
+ assertThrows(NullPointerException.class, () -> IntervalEnd.isValidEnd(null));
+
+ // invalid IntervalBegin
+ assertFalse(IntervalEnd.isValidEnd("9999")); // invalid
+ assertFalse(IntervalEnd.isValidEnd("")); // empty
+
+ // valid addresses
+ assertTrue(IntervalEnd.isValidEnd("1800"));
+ assertTrue(IntervalEnd.isValidEnd("0000"));
+ }
+
+ @Test
+ public void equals() {
+ IntervalEnd intervalEnd = new IntervalEnd("1800");
+
+ // same values -> returns true
+ assertTrue(intervalEnd.equals(new IntervalEnd("1800")));
+
+ // same object -> returns true
+ assertTrue(intervalEnd.equals(intervalEnd));
+
+ // null -> returns false
+ assertFalse(intervalEnd.equals(null));
+
+ // different types -> returns false
+ assertFalse(intervalEnd.equals(1800));
+
+ // different values -> returns false
+ assertFalse(intervalEnd.equals(new IntervalEnd("1900")));
+ }
+
+ @Test
+ public void toStringMethod() {
+ IntervalEnd intervalEnd = new IntervalEnd("2100");
+ assertTrue(intervalEnd.toString() == "2100");
+ }
+}
diff --git a/src/test/java/seedu/address/model/interval/IntervalTest.java b/src/test/java/seedu/address/model/interval/IntervalTest.java
new file mode 100644
index 00000000000..e383c220a4e
--- /dev/null
+++ b/src/test/java/seedu/address/model/interval/IntervalTest.java
@@ -0,0 +1,49 @@
+package seedu.address.model.interval;
+
+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 org.junit.jupiter.api.Test;
+
+public class IntervalTest {
+
+ @Test
+ public void equals() {
+ // same values -> returns true
+ Interval interval1 = new Interval(new IntervalDay("Mon"), new Duration("120"), new IntervalBegin("0800"),
+ new IntervalEnd("2200"));
+ Interval interval1Copy = new Interval(new IntervalDay("Mon"), new Duration("120"), new IntervalBegin("0800"),
+ new IntervalEnd("2200"));
+ Interval interval2 = new Interval(new IntervalDay("Sun"), new Duration("60"), new IntervalBegin("0900"),
+ new IntervalEnd("2300"));
+
+ assertTrue(interval1.equals(interval1Copy));
+
+ // same object -> returns true
+ assertTrue(interval1.equals(interval1));
+
+ // null -> returns false
+ assertFalse(interval1.equals(null));
+
+ // different type -> returns false
+ assertFalse(interval1.equals(5));
+
+ //different intervals
+ assertFalse(interval1.equals(interval2));
+
+ }
+
+ @Test
+ public void toStringMethod() {
+ Interval toTest = new Interval(new IntervalDay("Mon"), new Duration("120"), new IntervalBegin("0800"),
+ new IntervalEnd("2200"));
+
+ String expected = Interval.class.getCanonicalName() + "{intervalDay=" + toTest.getIntervalDay()
+ + ", duration=" + toTest.getDuration()
+ + ", intervalBegin=" + toTest.getIntervalBegin()
+ + ", intervalEnd=" + toTest.getIntervalEnd()
+ + "}";
+ assertEquals(expected, toTest.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/model/interval/TimeSlotTest.java b/src/test/java/seedu/address/model/interval/TimeSlotTest.java
new file mode 100644
index 00000000000..9527089f53b
--- /dev/null
+++ b/src/test/java/seedu/address/model/interval/TimeSlotTest.java
@@ -0,0 +1,149 @@
+package seedu.address.model.interval;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+public class TimeSlotTest {
+
+ private static SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm");
+
+ @Test
+ public void equals() throws ParseException {
+ //checks for same values and different values
+ TimeSlot timeSlot1 = new TimeSlot(dateFormat.parse("15:00"), dateFormat.parse("16:00"));
+ TimeSlot timeSlot2 = new TimeSlot(dateFormat.parse("15:00"), dateFormat.parse("16:00"));
+ TimeSlot timeSlot3 = new TimeSlot(dateFormat.parse("15:00"), dateFormat.parse("17:00"));
+
+ //check equal
+ assertEquals(timeSlot1, timeSlot2);
+
+ //check not equal
+ assertNotEquals(timeSlot1, timeSlot3);
+ }
+
+ @Test
+ public void equals2() throws ParseException {
+ //checks for same instance.
+ TimeSlot timeSlot = new TimeSlot(dateFormat.parse("15:00"), dateFormat.parse("16:00"));
+ assertEquals(timeSlot, timeSlot);
+ }
+
+ @Test
+ public void equals3() throws ParseException {
+ //checks for nulls
+ TimeSlot timeSlot = new TimeSlot(dateFormat.parse("15:00"), dateFormat.parse("16:00"));
+ assertNotEquals(timeSlot, null);
+ }
+
+
+ @Test
+ public void parseIntervals() throws ParseException {
+ List stringList = new ArrayList<>();
+ stringList.add("10:00 - 12:00");
+ stringList.add("14:00 - 16:00");
+ stringList.add("18:00 - 20:00");
+
+ List result = TimeSlot.parseIntervals(stringList);
+ List expected = new ArrayList<>();
+ expected.add(new TimeSlot(dateFormat.parse("10:00"), dateFormat.parse("12:00")));
+ expected.add(new TimeSlot(dateFormat.parse("14:00"), dateFormat.parse("16:00")));
+ expected.add(new TimeSlot(dateFormat.parse("18:00"), dateFormat.parse("20:00")));
+
+ List expectedWrong = new ArrayList<>();
+ expectedWrong.add(new TimeSlot(dateFormat.parse("10:00"), dateFormat.parse("14:00")));
+
+ assertEquals(expected, result);
+ assertNotEquals(expectedWrong, result);
+ }
+
+ @Test
+ public void findAvailableTime() throws ParseException {
+ // check when interval end is bigger than last end in timeslot
+
+ Interval interval = new Interval(new IntervalDay("Mon"), new Duration("30"), new IntervalBegin("0800"),
+ new IntervalEnd("2200"));
+
+ List timeslots = new ArrayList<>();
+ timeslots.add(new TimeSlot(dateFormat.parse("12:00"), dateFormat.parse("14:00")));
+ timeslots.add(new TimeSlot(dateFormat.parse("15:00"), dateFormat.parse("18:00")));
+
+ List result = TimeSlot.findAvailableTime(timeslots, interval);
+ List expected = new ArrayList<>();
+ expected.add(new TimeSlot(dateFormat.parse("08:00"), dateFormat.parse("12:00")));
+ expected.add(new TimeSlot(dateFormat.parse("14:00"), dateFormat.parse("15:00")));
+ expected.add(new TimeSlot(dateFormat.parse("18:00"), dateFormat.parse("22:00")));
+
+ assertEquals(expected, result);
+
+ Interval interval2 = new Interval(new IntervalDay("Mon"), new Duration("120"), new IntervalBegin("0800"),
+ new IntervalEnd("1900"));
+ List result2 = TimeSlot.findAvailableTime(timeslots, interval2);
+ List expected2 = new ArrayList<>();
+ expected2.add(new TimeSlot(dateFormat.parse("08:00"), dateFormat.parse("12:00")));
+ assertEquals(result2, expected2);
+ }
+
+ @Test
+ public void findAvailableTime2() throws ParseException {
+ //check when interval end is smaller than last end in timeslots.
+ List timeslots3 = new ArrayList<>();
+ timeslots3.add(new TimeSlot(dateFormat.parse("20:00"), dateFormat.parse("21:00")));
+
+ Interval interval3 = new Interval(new IntervalDay("Mon"), new Duration("120"), new IntervalBegin("0800"),
+ new IntervalEnd("1000"));
+
+ List expected3 = new ArrayList<>();
+ expected3.add(new TimeSlot(dateFormat.parse("08:00"), dateFormat.parse("10:00")));
+ List result3 = TimeSlot.findAvailableTime(timeslots3, interval3);
+ assertEquals(result3, expected3);
+ }
+
+ @Test
+ public void findAvailableTime3() throws ParseException {
+ //checks when duration is smaller than given the begin and end of intervals
+ List timeslots4 = new ArrayList<>();
+ timeslots4.add(new TimeSlot(dateFormat.parse("20:00"), dateFormat.parse("21:00")));
+
+ Interval interval4 = new Interval(new IntervalDay("Mon"), new Duration("180"), new IntervalBegin("0800"),
+ new IntervalEnd("1000"));
+ List expected4 = new ArrayList<>();
+ List result4 = TimeSlot.findAvailableTime(timeslots4, interval4);
+ assertEquals(result4, expected4);
+ }
+
+ @Test
+ public void printAvailableTime() throws ParseException {
+ //when zero results
+ List timeslots5 = new ArrayList<>();
+ timeslots5.add(new TimeSlot(dateFormat.parse("20:00"), dateFormat.parse("21:00")));
+
+ Interval interval5 = new Interval(new IntervalDay("Mon"), new Duration("180"), new IntervalBegin("0800"),
+ new IntervalEnd("1000"));
+ List result5 = TimeSlot.findAvailableTime(timeslots5, interval5);
+ String printedResult = TimeSlot.printResults(result5);
+ String expected = "There are no available timeslots.";
+ assertEquals(printedResult, expected);
+ }
+
+ @Test
+ public void printAvailableTime2() throws ParseException {
+ //when have results
+ List timeslots6 = new ArrayList<>();
+ timeslots6.add(new TimeSlot(dateFormat.parse("14:00"), dateFormat.parse("16:00")));
+
+ Interval interval6 = new Interval(new IntervalDay("Mon"), new Duration("120"), new IntervalBegin("0800"),
+ new IntervalEnd("2200"));
+ List result6 = TimeSlot.findAvailableTime(timeslots6, interval6);
+ String printedResult = TimeSlot.printResults(result6);
+ String expectedResult = "Free from 08:00 - 14:00\n" + "Free from 16:00 - 22:00\n";
+ assertEquals(printedResult, expectedResult);
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/person/BeginTest.java b/src/test/java/seedu/address/model/person/BeginTest.java
new file mode 100644
index 00000000000..fc0f4685bbb
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/BeginTest.java
@@ -0,0 +1,37 @@
+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.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.interval.IntervalBegin;
+
+public class BeginTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Begin(null));
+ }
+
+ @Test
+ public void equals() {
+ Begin begin = new Begin("1800");
+
+ // same values -> returns true
+ assertTrue(begin.equals(new Begin("1800")));
+
+ // same object -> returns true
+ assertTrue(begin.equals(begin));
+
+ // null -> returns false
+ assertFalse(begin.equals(null));
+
+ // different types -> returns false
+ assertFalse(begin.equals(1800));
+
+ // different values -> returns false
+ assertFalse(begin.equals(new IntervalBegin("1900")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/DayPredicateTest.java b/src/test/java/seedu/address/model/person/DayPredicateTest.java
new file mode 100644
index 00000000000..979d4160663
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/DayPredicateTest.java
@@ -0,0 +1,65 @@
+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 org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.PersonBuilder;
+
+
+public class DayPredicateTest {
+ @Test
+ public void equals() {
+ DayPredicate firstPredicate = new DayPredicate(new Day("Monday"));
+ DayPredicate secondPredicate = new DayPredicate(new Day("Wednesday"));
+
+ // same object -> returns true
+ assertTrue(firstPredicate.equals(firstPredicate));
+
+ // same values -> returns true
+ DayPredicate firstPredicateCopy = new DayPredicate(new Day("Mon"));
+ 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_personWithSameDay_returnsTrue() {
+ // Same day input
+ DayPredicate predicate = new DayPredicate(new Day("Monday"));
+ assertTrue(predicate.test(new PersonBuilder().withDay("Monday").build()));
+
+ // Short-form day input
+ predicate = new DayPredicate(new Day("Mon"));
+ assertTrue(predicate.test(new PersonBuilder().withDay("Monday").build()));
+
+ // Mixed-case day input
+ predicate = new DayPredicate(new Day("MOnDay"));
+ assertTrue(predicate.test(new PersonBuilder().withDay("Monday").build()));
+ }
+
+ @Test
+ public void test_personWithDifferentDay_returnsFalse() {
+ // Non-matching day
+ DayPredicate predicate = new DayPredicate(new Day("Monday"));
+ assertFalse(predicate.test(new PersonBuilder().withDay("Tue").build()));
+ }
+
+ @Test
+ public void toStringMethod() {
+ Day day = new Day("Mon");
+ DayPredicate predicate = new DayPredicate(new Day("Monday"));
+
+ String expected = DayPredicate.class.getCanonicalName() + "{day=" + day + "}";
+ assertEquals(expected, predicate.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/DayTest.java b/src/test/java/seedu/address/model/person/DayTest.java
new file mode 100644
index 00000000000..a28b5c4bde9
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/DayTest.java
@@ -0,0 +1,46 @@
+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.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class DayTest {
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Day(null));
+ }
+
+ @Test
+ public void constructor_invalidDay_throwsIllegalArgumentException() {
+ String emptyDay = "";
+ assertThrows(IllegalArgumentException.class, () -> new Day(emptyDay));
+
+ String invalidDay = "Tues";
+ assertThrows(IllegalArgumentException.class, () -> new PayRate(invalidDay));
+ }
+
+ @Test
+ public void equals() {
+ Day day = new Day("MON");
+
+ // same values -> returns true
+ assertTrue(day.equals(new Day("MON")));
+
+ // same values -> returns true
+ assertTrue(day.equals(new Day("Monday")));
+
+ // same object -> returns true
+ assertTrue(day.equals(day));
+
+ // null -> returns false
+ assertFalse(day.equals(null));
+
+ // different types -> returns false
+ assertFalse(day.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(day.equals(new Day("Tue")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/LessonComparatorTest.java b/src/test/java/seedu/address/model/person/LessonComparatorTest.java
new file mode 100644
index 00000000000..9180c30b54f
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/LessonComparatorTest.java
@@ -0,0 +1,42 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.DayOfWeek;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.PersonBuilder;
+
+public class LessonComparatorTest {
+
+ private final LessonComparator lessonComparator = new LessonComparator();
+
+ @Test
+ public void testCompareDaysCyclically() {
+ assertEquals(5, lessonComparator.compareDaysCyclically(DayOfWeek.MONDAY, DayOfWeek.WEDNESDAY));
+ assertEquals(4, lessonComparator.compareDaysCyclically(DayOfWeek.FRIDAY, DayOfWeek.MONDAY));
+ assertEquals(1, lessonComparator.compareDaysCyclically(DayOfWeek.SUNDAY, DayOfWeek.SATURDAY));
+ assertEquals(0, lessonComparator.compareDaysCyclically(DayOfWeek.THURSDAY, DayOfWeek.THURSDAY));
+ }
+
+ @Test
+ public void testCompareDifferentStartTimesSameDay() {
+ Person person1 = new PersonBuilder().withBegin("0900").withEnd("1000").build();
+ Person person2 = new PersonBuilder().withBegin("1100").withEnd("1200").build();
+
+ LessonComparator comparator = new LessonComparator();
+
+ assertEquals(-1, comparator.compare(person1, person2));
+ }
+
+ @Test
+ public void testCompareSameStartTimeSameDay() {
+ Person person1 = new PersonBuilder().build();
+ Person person2 = new PersonBuilder().build();
+
+ LessonComparator comparator = new LessonComparator();
+ assertEquals(0, comparator.compare(person1, person2));
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/person/LessonTest.java b/src/test/java/seedu/address/model/person/LessonTest.java
new file mode 100644
index 00000000000..761344b27cc
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/LessonTest.java
@@ -0,0 +1,88 @@
+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.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class LessonTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Lesson(null, null, null));
+ }
+
+ @Test
+ public void equals() {
+ Lesson lesson = new Lesson(new Day("thu"), new Begin("0800"), new End("1000"));
+
+ // same values -> returns true
+ assertTrue(lesson.equals(new Lesson(new Day("thu"), new Begin("0800"), new End("1000"))));
+
+ // same object -> returns true
+ assertTrue(lesson.equals(lesson));
+
+ // null -> returns false
+ assertFalse(lesson.equals(null));
+
+ // different type -> returns false
+ assertFalse(lesson.equals(9.0));
+
+ // different values -> returns false;
+ assertFalse(lesson.equals(new Lesson(new Day("fri"), new Begin("0800"), new End("1000"))));
+ assertFalse(lesson.equals(new Lesson(new Day("thu"), new Begin("0830"), new End("1000"))));
+ assertFalse(lesson.equals(new Lesson(new Day("thu"), new Begin("0800"), new End("1100"))));
+ }
+
+ @Test
+ public void getNumOfDaysInMonth() {
+ // Testing for October in 2023
+ Lesson monLesson = new Lesson(new Day("Mon"), new Begin("1200"), new End("1300"));
+ int expectedMonsInOctober2023 = 5;
+ assertEquals(expectedMonsInOctober2023, monLesson.getNumOfDaysInMonth(2023, 1));
+
+ // Testing for January in 2023 & 2024
+ Lesson wedLesson = new Lesson(new Day("Wed"), new Begin("1300"), new End("1400"));
+ int expectedDaysInJanuary2023 = 4; // January 2023 has 31 days
+ assertEquals(expectedDaysInJanuary2023, wedLesson.getNumOfDaysInMonth(2023, 1));
+
+ int expectedDaysInJanuary2024 = 5;
+ assertEquals(expectedDaysInJanuary2024, wedLesson.getNumOfDaysInMonth(2024, 1));
+
+ Lesson satLesson = new Lesson(new Day("Sat"), new Begin("1300"), new End("1400"));
+ int expectedSatsInOctober2023 = 4;
+ assertEquals(expectedSatsInOctober2023, satLesson.getNumOfDaysInMonth(2023, 10));
+
+ // Test for Leap Year 2024
+ Lesson thuLesson = new Lesson(new Day("Thu"), new Begin("1500"), new End("1530"));
+ int expectedThusInFeb2024 = 5;
+ assertEquals(expectedThusInFeb2024, thuLesson.getNumOfDaysInMonth(2024, 2));
+
+ // non leap years days always 4 on Feb
+ int expectedThusInFeb2023 = 4;
+ assertEquals(expectedThusInFeb2023, thuLesson.getNumOfDaysInMonth(2023, 2));
+
+ }
+
+ @Test
+ public void calculateLessonDuration() {
+ Lesson lessonOne = new Lesson(new Day("Mon"), new Begin("1200"), new End("1330"));
+ double expectedDurationOne = 1.5; // Expected hours between 8:00 AM and 4:00 PM is 8 hours
+ assertEquals(expectedDurationOne, lessonOne.calculateLessonDuration());
+
+ Lesson lessonTwo = new Lesson(new Day("Tue"), new Begin("1900"), new End("1920"));
+ double expectedDurationTwo = 1.0 / 3;
+ assertEquals(expectedDurationTwo, lessonTwo.calculateLessonDuration());
+
+ Lesson lessonThree = new Lesson(new Day("Sun"), new Begin("1500"), new End("1501"));
+ double expectedDurationThree = 1.0 / 60;
+ assertEquals(expectedDurationThree, lessonThree.calculateLessonDuration());
+
+ Lesson lessonFour = new Lesson(new Day("Thu"), new Begin("0000"), new End("2359"));
+ double expectedDurationFour = 1439.0 / 60;
+ assertEquals(expectedDurationFour, lessonFour.calculateLessonDuration());
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
index 6b3fd90ade7..0dda1613b8c 100644
--- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
+++ b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
@@ -41,8 +41,12 @@ public void equals() {
@Test
public void test_nameContainsKeywords_returnsTrue() {
+ //Zero keywords
+ NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.emptyList());
+ assertTrue(predicate.test(new PersonBuilder().withName("Alice").build()));
+
// One keyword
- NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(Collections.singletonList("Alice"));
+ predicate = new NameContainsKeywordsPredicate(Collections.singletonList("Alice"));
assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build()));
// Multiple keywords
@@ -60,12 +64,8 @@ public void test_nameContainsKeywords_returnsTrue() {
@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"));
+ NameContainsKeywordsPredicate 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
diff --git a/src/test/java/seedu/address/model/person/NameSubjectPredicateTest.java b/src/test/java/seedu/address/model/person/NameSubjectPredicateTest.java
new file mode 100644
index 00000000000..26765eb783e
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/NameSubjectPredicateTest.java
@@ -0,0 +1,90 @@
+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 java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.PersonBuilder;
+
+public class NameSubjectPredicateTest {
+
+ @Test
+ public void equals() {
+ List nameKeywords = Collections.singletonList("Alice");
+ List subjectKeywords = Arrays.asList("Math", "Science");
+
+ NameContainsKeywordsPredicate namePredicate = new NameContainsKeywordsPredicate(nameKeywords);
+ SubjectContainsKeywordsPredicate subjectPredicate = new SubjectContainsKeywordsPredicate(subjectKeywords);
+
+ NameSubjectPredicate firstPredicate = new NameSubjectPredicate(namePredicate, subjectPredicate);
+
+ assertTrue(firstPredicate.equals(firstPredicate));
+
+ NameSubjectPredicate firstPredicateCopy = new NameSubjectPredicate(namePredicate, subjectPredicate);
+ assertTrue(firstPredicate.equals(firstPredicateCopy));
+
+ assertFalse(firstPredicate.equals(1));
+
+ assertFalse(firstPredicate.equals(null));
+
+ NameSubjectPredicate secondPredicate = new NameSubjectPredicate(
+ new NameContainsKeywordsPredicate(Collections.singletonList("Bob")),
+ new SubjectContainsKeywordsPredicate(Collections.singletonList("English"))
+ );
+ assertFalse(firstPredicate.equals(secondPredicate));
+ }
+
+ @Test
+ public void test_nameSubjectPredicate_returnsTrue() {
+ //Both name and subject keywords match
+ NameContainsKeywordsPredicate name =
+ new NameContainsKeywordsPredicate(Collections.singletonList("Alice"));
+ SubjectContainsKeywordsPredicate subject =
+ new SubjectContainsKeywordsPredicate(Collections.singletonList("Maths"));
+ NameSubjectPredicate predicate = new NameSubjectPredicate(name, subject);
+ assertTrue(predicate.test(new PersonBuilder().withName("Alice").withSubject("Maths").build()));
+
+ }
+
+ @Test
+ public void test_nameSubjectPredicate_returnsFalse() {
+ // Neither name nor subject match
+ NameContainsKeywordsPredicate name =
+ new NameContainsKeywordsPredicate(Collections.singletonList("Alice"));
+ SubjectContainsKeywordsPredicate subject =
+ new SubjectContainsKeywordsPredicate(Collections.singletonList("Math"));
+ NameSubjectPredicate predicate = new NameSubjectPredicate(name, subject);
+ assertFalse(predicate.test(new PersonBuilder().withName("Bob").withSubject("English").build()));
+
+ //Only name keyword matches
+ name = new NameContainsKeywordsPredicate(Collections.singletonList("Alice"));
+ subject = new SubjectContainsKeywordsPredicate(Collections.singletonList("Biology"));
+ predicate = new NameSubjectPredicate(name, subject);
+ assertFalse(predicate.test(new PersonBuilder().withName("Alice").withSubject("Maths").build()));
+
+ //Only subject keyword matches
+ name = new NameContainsKeywordsPredicate(Collections.singletonList("Bob"));
+ subject = new SubjectContainsKeywordsPredicate(Collections.singletonList("Math"));
+ predicate = new NameSubjectPredicate(name, subject);
+ assertFalse(predicate.test(new PersonBuilder().withName("Alice").withSubject("Math").build()));
+ }
+
+ @Test
+ public void toStringMethod() {
+ List nameKeywords = Collections.singletonList("Alice");
+ List subjectKeywords = Arrays.asList("Math", "Science");
+ NameContainsKeywordsPredicate namePredicate = new NameContainsKeywordsPredicate(nameKeywords);
+ SubjectContainsKeywordsPredicate subjectPredicate = new SubjectContainsKeywordsPredicate(subjectKeywords);
+ NameSubjectPredicate predicate = new NameSubjectPredicate(namePredicate, subjectPredicate);
+
+ String expected = NameSubjectPredicate.class.getCanonicalName() + "{name=" + namePredicate + ", subject="
+ + subjectPredicate + "}";
+ assertEquals(expected, predicate.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/PaidPredicateTest.java b/src/test/java/seedu/address/model/person/PaidPredicateTest.java
new file mode 100644
index 00000000000..9ea667f4323
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/PaidPredicateTest.java
@@ -0,0 +1,28 @@
+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 PaidPredicateTest {
+ @Test
+ public void equals() {
+
+ PaidPredicate firstPredicate = new PaidPredicate(true);
+ PaidPredicate secondPredicate = new PaidPredicate(true);
+
+ // same object -> returns true
+ assertTrue(firstPredicate.equals(firstPredicate));
+
+ // same values -> returns true
+ PaidPredicate firstPredicateCopy = new PaidPredicate(true);
+ assertTrue(firstPredicate.equals(firstPredicateCopy));
+
+ // different types -> returns false
+ assertFalse(firstPredicate.equals(1));
+
+ // null -> returns false
+ assertFalse(firstPredicate.equals(null));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/PayRateTest.java b/src/test/java/seedu/address/model/person/PayRateTest.java
new file mode 100644
index 00000000000..e344dd2e365
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/PayRateTest.java
@@ -0,0 +1,49 @@
+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.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class PayRateTest {
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new PayRate(null));
+ }
+
+ @Test
+ public void constructor_invalidPayRate_throwsIllegalArgumentException() {
+ String emptyPayRate = "";
+ assertThrows(IllegalArgumentException.class, () -> new PayRate(emptyPayRate));
+
+ String invalidPayRate = "abc";
+ assertThrows(IllegalArgumentException.class, () -> new PayRate(invalidPayRate));
+
+ String negativePayRate = "-2.30";
+ assertThrows(IllegalArgumentException.class, () -> new PayRate(negativePayRate));
+
+ String negativeZeroPayRate = "-0";
+ assertThrows(IllegalArgumentException.class, () -> new PayRate(negativeZeroPayRate));
+ }
+
+ @Test
+ public void equals() {
+ PayRate payRate = new PayRate("23");
+
+ // same values -> returns true
+ assertTrue(payRate.equals(new PayRate("23")));
+
+ // same object -> returns true
+ assertTrue(payRate.equals(payRate));
+
+ // null -> returns false
+ assertFalse(payRate.equals(null));
+
+ // different types -> returns false
+ assertFalse(payRate.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(payRate.equals(new PayRate("23.1")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java
index 31a10d156c9..3c7374ffd75 100644
--- a/src/test/java/seedu/address/model/person/PersonTest.java
+++ b/src/test/java/seedu/address/model/person/PersonTest.java
@@ -2,13 +2,13 @@
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.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_PAYRATE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
-import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BOB;
@@ -17,13 +17,6 @@
import seedu.address.testutil.PersonBuilder;
public class PersonTest {
-
- @Test
- public void asObservableList_modifyList_throwsUnsupportedOperationException() {
- Person person = new PersonBuilder().build();
- assertThrows(UnsupportedOperationException.class, () -> person.getTags().remove(0));
- }
-
@Test
public void isSamePerson() {
// same object -> returns true
@@ -32,11 +25,16 @@ 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)
- .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build();
+ // same name, same phone number all other attributes different -> returns true
+ Person editedAlice = new PersonBuilder(ALICE).withPhone(ALICE.getPhone().toString()).withEmail(VALID_EMAIL_BOB)
+ .withAddress(VALID_ADDRESS_BOB).build();
assertTrue(ALICE.isSamePerson(editedAlice));
+ // same name, all other attributes different -> returns false
+ editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB)
+ .withAddress(VALID_ADDRESS_BOB).build();
+ assertFalse(ALICE.isSamePerson(editedAlice));
+
// different name, all other attributes same -> returns false
editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build();
assertFalse(ALICE.isSamePerson(editedAlice));
@@ -85,15 +83,33 @@ public void equals() {
editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build();
assertFalse(ALICE.equals(editedAlice));
- // different tags -> returns false
- editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build();
+ // different payrate -> returns false
+ editedAlice = new PersonBuilder(ALICE).withPayRate(VALID_PAYRATE_BOB).build();
assertFalse(ALICE.equals(editedAlice));
}
@Test
public void toStringMethod() {
String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone()
- + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags() + "}";
+ + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", subject=" + ALICE.getSubject()
+ + ", lesson=" + ALICE.getLesson() + ", paid=" + ALICE.getPaid()
+ + ", payrate=" + ALICE.getPayRate() + "}";
assertEquals(expected, ALICE.toString());
}
+
+ @Test
+ public void getMonthlyFeeMethod() {
+ Person alice = new PersonBuilder(ALICE).withDay("Mon").withBegin("1000").withEnd("1004")
+ .withPayRate("1.52").build();
+ Person bob = new PersonBuilder(BOB).withDay("Mon").withBegin("1000").withEnd("1004")
+ .withPayRate("1.52").build();
+
+ // persons with same lesson and payrate should return same monthly revenue
+ assertEquals(alice.getMonthlyFee(), bob.getMonthlyFee());
+
+ Person modifiedAlice = new PersonBuilder(alice).withPayRate("1.53").build();
+ assertNotEquals(modifiedAlice.getMonthlyFee(), alice.getMonthlyFee());
+
+ }
+
}
diff --git a/src/test/java/seedu/address/model/person/SubjectContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/SubjectContainsKeywordsPredicateTest.java
new file mode 100644
index 00000000000..f42fb807223
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/SubjectContainsKeywordsPredicateTest.java
@@ -0,0 +1,91 @@
+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 java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.PersonBuilder;
+
+public class SubjectContainsKeywordsPredicateTest {
+
+ @Test
+ public void equals() {
+ List firstPredicateKeywordList = Collections.singletonList("first");
+ List secondPredicateKeywordList = Arrays.asList("first", "second");
+
+ SubjectContainsKeywordsPredicate firstPredicate =
+ new SubjectContainsKeywordsPredicate(firstPredicateKeywordList);
+ SubjectContainsKeywordsPredicate secondPredicate =
+ new SubjectContainsKeywordsPredicate(secondPredicateKeywordList);
+
+ // same object -> returns true
+ assertTrue(firstPredicate.equals(firstPredicate));
+
+ // same values -> returns true
+ SubjectContainsKeywordsPredicate firstPredicateCopy =
+ new SubjectContainsKeywordsPredicate(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_subjectContainsKeywords_returnsTrue() {
+ // Zero keywords
+ SubjectContainsKeywordsPredicate subject = new SubjectContainsKeywordsPredicate(Collections.emptyList());
+ assertTrue(subject.test(new PersonBuilder().withSubject("Maths").build()));
+
+ // One keyword
+ SubjectContainsKeywordsPredicate predicate = new SubjectContainsKeywordsPredicate(
+ Collections.singletonList("Maths"));
+ assertTrue(predicate.test(new PersonBuilder().withSubject("Maths Chemistry").build()));
+
+ // Multiple keywords
+ predicate = new SubjectContainsKeywordsPredicate(Arrays.asList("Maths", "Chemistry"));
+ assertTrue(predicate.test(new PersonBuilder().withSubject("Maths Chemistry").build()));
+
+ // Only one matching keyword
+ predicate = new SubjectContainsKeywordsPredicate(Arrays.asList("Chemistry", "Biology"));
+ assertTrue(predicate.test(new PersonBuilder().withSubject("Chemistry Biology").build()));
+
+ // Mixed-case keywords
+ predicate = new SubjectContainsKeywordsPredicate(Arrays.asList("mAtHs", "cheMistRy"));
+ assertTrue(predicate.test(new PersonBuilder().withSubject("Maths Chemistry").build()));
+ }
+
+ @Test
+ public void test_subjectDoesNotContainKeywords_returnsFalse() {
+ // Non-matching keyword
+ SubjectContainsKeywordsPredicate subject = new SubjectContainsKeywordsPredicate(Arrays.asList("Biology"));
+ assertFalse(subject.test(new PersonBuilder().withSubject("Maths Chemistry").build()));
+
+ // Keywords match phone, email and address, but does not match name
+ subject = new SubjectContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street"));
+ assertFalse(subject.test(new PersonBuilder().withSubject("Maths").withPhone("12345")
+ .withEmail("alice@email.com").withAddress("Main Street").build()));
+ }
+
+
+ @Test
+ public void toStringMethod() {
+ List keywords = List.of("keyword1", "keyword2");
+ SubjectContainsKeywordsPredicate predicate = new SubjectContainsKeywordsPredicate(keywords);
+
+ String expected = SubjectContainsKeywordsPredicate.class.getCanonicalName() + "{keywords=" + keywords + "}";
+ assertEquals(expected, predicate.toString());
+ }
+}
+
diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
index 17ae501df08..8ea36cdf4b0 100644
--- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java
+++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
@@ -4,17 +4,24 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BOB;
+import java.text.ParseException;
+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.model.interval.Duration;
+import seedu.address.model.interval.Interval;
+import seedu.address.model.interval.IntervalBegin;
+import seedu.address.model.interval.IntervalDay;
+import seedu.address.model.interval.IntervalEnd;
+import seedu.address.model.person.exceptions.ClashingScheduleException;
import seedu.address.model.person.exceptions.DuplicatePersonException;
import seedu.address.model.person.exceptions.PersonNotFoundException;
import seedu.address.testutil.PersonBuilder;
@@ -23,6 +30,16 @@ public class UniquePersonListTest {
private final UniquePersonList uniquePersonList = new UniquePersonList();
+ @Test void testFindInterval() throws ParseException {
+ Interval interval = new Interval(new IntervalDay("Mon"), new Duration("60"), new IntervalBegin("0600"),
+ new IntervalEnd("2200"));
+ uniquePersonList.add(ALICE);
+ List result = uniquePersonList.findInterval(interval);
+ List expected = new ArrayList<>();
+ expected.add("08:00 - 10:00");
+ assertEquals(expected, result);
+ }
+
@Test
public void contains_nullPerson_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> uniquePersonList.contains(null));
@@ -42,8 +59,7 @@ public void contains_personInList_returnsTrue() {
@Test
public void contains_personWithSameIdentityFieldsInList_returnsTrue() {
uniquePersonList.add(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
- .build();
+ Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build();
assertTrue(uniquePersonList.contains(editedAlice));
}
@@ -58,25 +74,34 @@ public void add_duplicatePerson_throwsDuplicatePersonException() {
assertThrows(DuplicatePersonException.class, () -> uniquePersonList.add(ALICE));
}
+ @Test
+ public void add_clashingSchedule_throwsClashingScheduleException() {
+ uniquePersonList.add(ALICE);
+ Person clashingSchedule = new PersonBuilder().withDay(ALICE.getDay().toString())
+ .withBegin(ALICE.getBegin().toString())
+ .withEnd(ALICE.getEnd().toString()).build();
+ assertThrows(ClashingScheduleException.class, () -> uniquePersonList.add(clashingSchedule));
+ }
+
@Test
public void setPerson_nullTargetPerson_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> uniquePersonList.setPerson(null, ALICE));
+ assertThrows(NullPointerException.class, () -> uniquePersonList.setPerson(null, ALICE, false));
}
@Test
public void setPerson_nullEditedPerson_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> uniquePersonList.setPerson(ALICE, null));
+ assertThrows(NullPointerException.class, () -> uniquePersonList.setPerson(ALICE, null, false));
}
@Test
public void setPerson_targetPersonNotInList_throwsPersonNotFoundException() {
- assertThrows(PersonNotFoundException.class, () -> uniquePersonList.setPerson(ALICE, ALICE));
+ assertThrows(PersonNotFoundException.class, () -> uniquePersonList.setPerson(ALICE, ALICE, false));
}
@Test
public void setPerson_editedPersonIsSamePerson_success() {
uniquePersonList.add(ALICE);
- uniquePersonList.setPerson(ALICE, ALICE);
+ uniquePersonList.setPerson(ALICE, ALICE, false);
UniquePersonList expectedUniquePersonList = new UniquePersonList();
expectedUniquePersonList.add(ALICE);
assertEquals(expectedUniquePersonList, uniquePersonList);
@@ -85,9 +110,8 @@ public void setPerson_editedPersonIsSamePerson_success() {
@Test
public void setPerson_editedPersonHasSameIdentity_success() {
uniquePersonList.add(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
- .build();
- uniquePersonList.setPerson(ALICE, editedAlice);
+ Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build();
+ uniquePersonList.setPerson(ALICE, editedAlice, false);
UniquePersonList expectedUniquePersonList = new UniquePersonList();
expectedUniquePersonList.add(editedAlice);
assertEquals(expectedUniquePersonList, uniquePersonList);
@@ -96,7 +120,7 @@ public void setPerson_editedPersonHasSameIdentity_success() {
@Test
public void setPerson_editedPersonHasDifferentIdentity_success() {
uniquePersonList.add(ALICE);
- uniquePersonList.setPerson(ALICE, BOB);
+ uniquePersonList.setPerson(ALICE, BOB, false);
UniquePersonList expectedUniquePersonList = new UniquePersonList();
expectedUniquePersonList.add(BOB);
assertEquals(expectedUniquePersonList, uniquePersonList);
@@ -106,7 +130,19 @@ public void setPerson_editedPersonHasDifferentIdentity_success() {
public void setPerson_editedPersonHasNonUniqueIdentity_throwsDuplicatePersonException() {
uniquePersonList.add(ALICE);
uniquePersonList.add(BOB);
- assertThrows(DuplicatePersonException.class, () -> uniquePersonList.setPerson(ALICE, BOB));
+ assertThrows(DuplicatePersonException.class, () -> uniquePersonList.setPerson(ALICE, BOB, true));
+ }
+
+ @Test
+ public void setPerson_editPersonWithClashingSchedule_throwsDuplicatePersonException() {
+ uniquePersonList.add(ALICE);
+ uniquePersonList.add(BOB);
+ Person clashingSchedule = new PersonBuilder()
+ .withDay(ALICE.getDay().toString())
+ .withBegin(ALICE.getBegin().toString())
+ .withEnd(ALICE.getEnd().toString())
+ .build();
+ assertThrows(ClashingScheduleException.class, () -> uniquePersonList.setPerson(BOB, clashingSchedule, true));
}
@Test
diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java
deleted file mode 100644
index 64d07d79ee2..00000000000
--- a/src/test/java/seedu/address/model/tag/TagTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package seedu.address.model.tag;
-
-import static seedu.address.testutil.Assert.assertThrows;
-
-import org.junit.jupiter.api.Test;
-
-public class TagTest {
-
- @Test
- public void constructor_null_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> new Tag(null));
- }
-
- @Test
- public void constructor_invalidTagName_throwsIllegalArgumentException() {
- String invalidTagName = "";
- assertThrows(IllegalArgumentException.class, () -> new Tag(invalidTagName));
- }
-
- @Test
- public void isValidTagName() {
- // null tag name
- assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null));
- }
-
-}
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
index 83b11331cdb..aca0048335a 100644
--- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
+++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
@@ -5,32 +5,39 @@
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.BENSON;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
import org.junit.jupiter.api.Test;
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Begin;
+import seedu.address.model.person.Day;
import seedu.address.model.person.Email;
+import seedu.address.model.person.End;
import seedu.address.model.person.Name;
+import seedu.address.model.person.PayRate;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.Subject;
public class JsonAdaptedPersonTest {
private static final String INVALID_NAME = "R@chel";
private static final String INVALID_PHONE = "+651234";
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_SUBJECT = " ";
+ private static final String INVALID_DAY = "Mond";
+ private static final String INVALID_BEGIN = "8888";
+ private static final String INVALID_END = "9999";
+ private static final String INVALID_PAYRATE = "hundred";
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 List VALID_TAGS = BENSON.getTags().stream()
- .map(JsonAdaptedTag::new)
- .collect(Collectors.toList());
+ private static final String VALID_SUBJECT = BENSON.getSubject().toString();
+ private static final String VALID_DAY = BENSON.getDay().toString();
+ private static final String VALID_BEGIN = BENSON.getBegin().toString();
+ private static final String VALID_END = BENSON.getEnd().toString();
+ private static final String VALID_PAYRATE = BENSON.getPayRate().toString();
@Test
public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@@ -41,14 +48,16 @@ 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_SUBJECT,
+ VALID_DAY, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
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_SUBJECT, VALID_DAY, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -56,14 +65,16 @@ 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_SUBJECT, VALID_DAY, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
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_SUBJECT, VALID_DAY, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -71,14 +82,16 @@ 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_SUBJECT, VALID_DAY, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
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_SUBJECT, VALID_DAY, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -86,25 +99,100 @@ 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_SUBJECT, VALID_DAY, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
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_SUBJECT, VALID_DAY, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
- public void toModelType_invalidTags_throwsIllegalValueException() {
- List invalidTags = new ArrayList<>(VALID_TAGS);
- invalidTags.add(new JsonAdaptedTag(INVALID_TAG));
+ public void toModelType_invalidSubject_throwsIllegalValueException() {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ INVALID_SUBJECT, VALID_DAY, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
+ String expectedMessage = Subject.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullSubject_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ null, VALID_DAY, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Subject.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidDay_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags);
- assertThrows(IllegalValueException.class, person::toModelType);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_SUBJECT, INVALID_DAY, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
+ String expectedMessage = Day.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullDay_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_SUBJECT, null, VALID_BEGIN, VALID_END, false, VALID_PAYRATE);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Day.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidBegin_throwsIllegalValueException() {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_SUBJECT, VALID_DAY, INVALID_BEGIN, VALID_END, false, VALID_PAYRATE);
+ String expectedMessage = Begin.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
+ @Test
+ public void toModelType_nullBegin_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_SUBJECT, VALID_DAY, null, VALID_END, false, VALID_PAYRATE);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Begin.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidEnd_throwsIllegalValueException() {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_SUBJECT, VALID_DAY, VALID_BEGIN, INVALID_END, false, VALID_PAYRATE);
+ String expectedMessage = End.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullEnd_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_SUBJECT, VALID_DAY, VALID_BEGIN, null, false, VALID_PAYRATE);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, End.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidPayRate_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_SUBJECT, VALID_DAY, VALID_BEGIN, VALID_END, false, INVALID_PAYRATE);
+ String expectedMessage = PayRate.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+ @Test
+ public void toModelType_nullPayRate_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_SUBJECT, VALID_DAY, VALID_BEGIN, VALID_END, false, null);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, PayRate.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
}
diff --git a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java b/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java
index ed0a413526a..5000d416fec 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 DataLoad
private UserPrefs getTypicalUserPrefs() {
UserPrefs userPrefs = new UserPrefs();
userPrefs.setGuiSettings(new GuiSettings(1000, 500, 300, 100));
- userPrefs.setAddressBookFilePath(Paths.get("addressbook.json"));
+ userPrefs.setAddressBookFilePath(Paths.get("tuitionconnect.json"));
return userPrefs;
}
diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
index 4584bd5044e..6989ca8fde3 100644
--- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
+++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
@@ -1,16 +1,16 @@
package seedu.address.testutil;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Begin;
+import seedu.address.model.person.Day;
import seedu.address.model.person.Email;
+import seedu.address.model.person.End;
import seedu.address.model.person.Name;
+import seedu.address.model.person.PayRate;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.Subject;
/**
* A utility class to help with building EditPersonDescriptor objects.
@@ -36,7 +36,12 @@ public EditPersonDescriptorBuilder(Person person) {
descriptor.setPhone(person.getPhone());
descriptor.setEmail(person.getEmail());
descriptor.setAddress(person.getAddress());
- descriptor.setTags(person.getTags());
+ descriptor.setSubject(person.getSubject());
+ descriptor.setDay(person.getDay());
+ descriptor.setBegin(person.getBegin());
+ descriptor.setEnd(person.getEnd());
+ descriptor.setPaid(null);
+ descriptor.setPayRate(person.getPayRate());
}
/**
@@ -71,13 +76,44 @@ public EditPersonDescriptorBuilder withAddress(String address) {
return this;
}
+ /**
+ * Sets the {@code Subject} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withSubject(String subject) {
+ descriptor.setSubject(new Subject(subject));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Day} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withDay(String day) {
+ descriptor.setDay(new Day(day));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Begin} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withBegin(String begin) {
+ descriptor.setBegin(new Begin(begin));
+ return this;
+ }
+
+ /**
+ * Sets the {@code End} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withEnd(String end) {
+ descriptor.setEnd(new End(end));
+ return this;
+ }
+
/**
* Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor}
* that we are building.
*/
- public EditPersonDescriptorBuilder withTags(String... tags) {
- Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet());
- descriptor.setTags(tagSet);
+ public EditPersonDescriptorBuilder withPayRate(String payRate) {
+ descriptor.setPayRate(new PayRate(payRate));
return this;
}
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..6eac9ca3c31
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/ModelStub.java
@@ -0,0 +1,153 @@
+package seedu.address.testutil;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Predicate;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.Model;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.interval.Interval;
+import seedu.address.model.person.Person;
+
+/**
+ * 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 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 boolean hasDate(Person person) {
+ throw new AssertionError("This hasDate method should not be called.");
+ }
+
+ @Override
+ public List findInterval(Interval interval) {
+ throw new AssertionError("This findInterval method should not be called");
+ }
+
+ @Override
+ public void deletePerson(Person target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void markPersonPaid(Person target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void markPersonUnPaid(Person target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean getPersonPaid(Person target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void commitAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void undoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void redoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean canUndoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean canRedoAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void purgeAddressBook() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setPerson(Person target, Person editedPerson, boolean isEditingSchedule) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredPersonList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getScheduleList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getUnfilteredPersonList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateFilteredPersonList(Predicate predicate) {
+ 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..f650a8b9af0 100644
--- a/src/test/java/seedu/address/testutil/PersonBuilder.java
+++ b/src/test/java/seedu/address/testutil/PersonBuilder.java
@@ -1,15 +1,15 @@
package seedu.address.testutil;
-import java.util.HashSet;
-import java.util.Set;
-
import seedu.address.model.person.Address;
+import seedu.address.model.person.Begin;
+import seedu.address.model.person.Day;
import seedu.address.model.person.Email;
+import seedu.address.model.person.End;
import seedu.address.model.person.Name;
+import seedu.address.model.person.PayRate;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-import seedu.address.model.util.SampleDataUtil;
+import seedu.address.model.person.Subject;
/**
* A utility class to help with building Person objects.
@@ -20,12 +20,24 @@ 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_SUBJECT = "Maths";
+ public static final String DEFAULT_DAY = "Mon";
+ public static final String DEFAULT_BEGIN = "1000";
+ public static final String DEFAULT_END = "1200";
+
+ public static final String DEFAULT_PAYRATE = "15.00";
private Name name;
private Phone phone;
private Email email;
private Address address;
- private Set tags;
+ private Subject subject;
+ private Day day;
+ private Begin begin;
+ private End end;
+ private boolean paid;
+
+ private PayRate payRate;
/**
* Creates a {@code PersonBuilder} with the default details.
@@ -35,7 +47,12 @@ public PersonBuilder() {
phone = new Phone(DEFAULT_PHONE);
email = new Email(DEFAULT_EMAIL);
address = new Address(DEFAULT_ADDRESS);
- tags = new HashSet<>();
+ subject = new Subject(DEFAULT_SUBJECT);
+ day = new Day(DEFAULT_DAY);
+ begin = new Begin(DEFAULT_BEGIN);
+ end = new End(DEFAULT_END);
+ paid = false;
+ payRate = new PayRate(DEFAULT_PAYRATE);
}
/**
@@ -46,7 +63,12 @@ public PersonBuilder(Person personToCopy) {
phone = personToCopy.getPhone();
email = personToCopy.getEmail();
address = personToCopy.getAddress();
- tags = new HashSet<>(personToCopy.getTags());
+ subject = personToCopy.getSubject();
+ day = personToCopy.getDay();
+ begin = personToCopy.getBegin();
+ end = personToCopy.getEnd();
+ paid = false;
+ payRate = personToCopy.getPayRate();
}
/**
@@ -57,14 +79,6 @@ public PersonBuilder withName(String name) {
return this;
}
- /**
- * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building.
- */
- public PersonBuilder withTags(String ... tags) {
- this.tags = SampleDataUtil.getTagSet(tags);
- return this;
- }
-
/**
* Sets the {@code Address} of the {@code Person} that we are building.
*/
@@ -89,8 +103,48 @@ public PersonBuilder withEmail(String email) {
return this;
}
+ /**
+ * Sets the {@code Subject} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withSubject(String subject) {
+ this.subject = new Subject(subject);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Day} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withDay(String day) {
+ this.day = new Day(day);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Begin} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withBegin(String begin) {
+ this.begin = new Begin(begin);
+ return this;
+ }
+
+ /**
+ * Sets the {@code End} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withEnd(String end) {
+ this.end = new End(end);
+ return this;
+ }
+
+ /**
+ * Sets the {@code PayRate} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withPayRate(String payRate) {
+ this.payRate = new PayRate(payRate);
+ return this;
+ }
+
public Person build() {
- return new Person(name, phone, email, address, tags);
+ return new Person(name, phone, email, address, subject, day, begin, end, paid, payRate);
}
}
diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java
index 90849945183..ad2164866e3 100644
--- a/src/test/java/seedu/address/testutil/PersonUtil.java
+++ b/src/test/java/seedu/address/testutil/PersonUtil.java
@@ -1,18 +1,18 @@
package seedu.address.testutil;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_BEGIN;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_END;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import java.util.Set;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.model.person.Person;
-import seedu.address.model.tag.Tag;
-
/**
* A utility class for Person.
*/
@@ -34,9 +34,11 @@ 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 + " ")
- );
+ sb.append(PREFIX_SUBJECT + person.getSubject().value + " ");
+ sb.append(PREFIX_DAY + person.getDay().value.toString() + " ");
+ sb.append(PREFIX_BEGIN + person.getBegin().value + " ");
+ sb.append(PREFIX_END + person.getEnd().value + " ");
+ sb.append(PREFIX_PAYRATE + person.getPayRate().toString() + " ");
return sb.toString();
}
@@ -49,14 +51,11 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip
descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" "));
descriptor.getEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" "));
descriptor.getAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" "));
- if (descriptor.getTags().isPresent()) {
- Set tags = descriptor.getTags().get();
- if (tags.isEmpty()) {
- sb.append(PREFIX_TAG);
- } else {
- tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" "));
- }
- }
+ descriptor.getSubject().ifPresent(subject -> sb.append(PREFIX_SUBJECT).append(subject.value).append(" "));
+ descriptor.getDay().ifPresent(day -> sb.append(PREFIX_DAY).append(day.value).append(" "));
+ descriptor.getBegin().ifPresent(begin -> sb.append(PREFIX_BEGIN).append(begin.value).append(" "));
+ descriptor.getEnd().ifPresent(end -> sb.append(PREFIX_END).append(end.value).append(" "));
+ descriptor.getPayRate().ifPresent(payRate -> sb.append(PREFIX_PAYRATE).append(payRate.toString()).append(" "));
return sb.toString();
}
}
diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java
index fec76fb7129..0b6a8e637ce 100644
--- a/src/test/java/seedu/address/testutil/TypicalPersons.java
+++ b/src/test/java/seedu/address/testutil/TypicalPersons.java
@@ -2,20 +2,29 @@
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_BEGIN_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_BEGIN_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DAY_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_DAY_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_END_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_END_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_PAYRATE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PAYRATE_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.commands.CommandTestUtil.VALID_SUBJECT_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_BOB;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import seedu.address.model.AddressBook;
+import seedu.address.model.VersionedAddressBook;
import seedu.address.model.person.Person;
/**
@@ -26,34 +35,89 @@ public class TypicalPersons {
public static final Person ALICE = new PersonBuilder().withName("Alice Pauline")
.withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com")
.withPhone("94351253")
- .withTags("friends").build();
+ .withSubject("Maths")
+ .withDay("Mon")
+ .withBegin("0800")
+ .withEnd("1000")
+ .withPayRate("20.00")
+ .build();
public static final Person BENSON = new PersonBuilder().withName("Benson Meier")
.withAddress("311, Clementi Ave 2, #02-25")
.withEmail("johnd@example.com").withPhone("98765432")
- .withTags("owesMoney", "friends").build();
+ .withSubject("Chemistry")
+ .withDay("Tue")
+ .withBegin("0900")
+ .withEnd("1100")
+ .withPayRate("15.00")
+ .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")
+ .withSubject("Biology")
+ .withDay("Thu")
+ .withBegin("1800")
+ .withEnd("2000")
+ .withPayRate("25.00")
+ .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")
+ .withSubject("Physics")
+ .withDay("Thu")
+ .withBegin("1100")
+ .withEnd("1300")
+ .withPayRate("35.00")
+ .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")
+ .withSubject("Maths")
+ .withDay("Fri")
+ .withBegin("1200")
+ .withEnd("1400")
+ .withPayRate("50.00")
+ .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")
+ .withSubject("English")
+ .withDay("Sat")
+ .withBegin("1300")
+ .withEnd("1500")
+ .withPayRate("40.00")
+ .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")
+ .withSubject("Chinese")
+ .withDay("Sun")
+ .withBegin("1400")
+ .withEnd("1620")
+ .withPayRate("30.00")
+ .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")
+ .withSubject("Maths")
+ .withDay("Mon")
+ .withBegin("1600")
+ .withEnd("1800")
+ .withPayRate("10.00")
+ .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")
+ .withSubject("English")
+ .withDay("Tue")
+ .withBegin("1600")
+ .withEnd("1800")
+ .withPayRate("60.00")
+ .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)
+ .withSubject(VALID_SUBJECT_AMY).withDay(VALID_DAY_AMY).withBegin(VALID_BEGIN_AMY)
+ .withEnd(VALID_END_AMY).withPayRate(VALID_PAYRATE_AMY).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();
+ .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_BOB)
+ .withDay(VALID_DAY_BOB).withBegin(VALID_BEGIN_BOB).withEnd(VALID_END_BOB)
+ .withPayRate(VALID_PAYRATE_BOB).build();
public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER
@@ -70,6 +134,17 @@ public static AddressBook getTypicalAddressBook() {
return ab;
}
+ /**
+ * Returns an {@code VersionedAddressBook} with all the typical persons.
+ */
+ public static VersionedAddressBook getTypicalVersionedAddressBook() {
+ VersionedAddressBook vab = new VersionedAddressBook();
+ for (Person person : getTypicalPersons()) {
+ vab.addPerson(person);
+ }
+ return vab;
+ }
+
public static List getTypicalPersons() {
return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE));
}