diff --git a/.gitignore b/.gitignore index 284c4ca7cd9..5320d395241 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ src/main/resources/docs/ # IDEA files /.idea/ /out/ +/bin/ /*.iml # Storage/log files diff --git a/README.md b/README.md index 13f5c77403f..896de4fb7ca 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,24 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2324S1-CS2103T-W11-2/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S1-CS2103T-W11-2/tp/actions) + +[![codecov](https://codecov.io/gh/AY2324S1-CS2103T-W11-2/tp/graph/badge.svg?token=CM2O49C5RT)](https://codecov.io/gh/AY2324S1-CS2103T-W11-2/tp) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. +# PropertyMatch +Are you a new property agent starting to jump to the real estate world? +Do you feel overwhelmed of remembering properties' features and your clients' preferences? +We have the right tool for you! + +Introducing PropertyMatch, the real estate tracking application made just for you! + +## Features +- [ ] Add new properties +- [ ] Add new customers +- [ ] Delete existing properties +- [ ] Delete existing customers +- [ ] List all properties +- [ ] List all customers + +## Others * This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). diff --git a/build.gradle b/build.gradle index a2951cc709e..5e3122d3753 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,11 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'propertymatch.jar' +} + +run { + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index eb761a9b9a7..c7640d8f4f9 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -57,7 +57,7 @@ --> + value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE"/> @@ -216,7 +216,7 @@ some other variants which we don't publicized to promote consistency). --> + value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/> diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..83d5be08734 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,51 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Loh Jian Rong - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/jianrong7)] +[[portfolio](team/jianrong7.md)] -* Role: Project Advisor +* Role: Scheduling and Tracking, Integration +* Responsibilities: Logic, UI -### Jane Doe +### Nicole Ng - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/nicolengk)] +[[portfolio](team/nicolengk.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Code Quality +* Responsibilities: UI, Logic -### Johnny Doe +### Sara Ong - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/saraozn)] [[portfolio](team/saraozn.md)] -* Role: Developer -* Responsibilities: Data +* Role: Deliverables and deadlines +* Responsibilities: Ensure project deliverables are done on time and in the right format. -### Jean Doe +### Ferdinand Halim Santoso - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/ferdihs)] +[[portfolio](team/ferdihs.md)] -* Role: Developer -* Responsibilities: Dev Ops + Threading +* Role: Developer and Tester +* Responsibilities: Make sure the application runs properly -### James Doe +### Fredy Lawrence - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/chrainx)] +[[portfolio](team/chrainx.md)] -* Role: Developer -* Responsibilities: UI +* Role: Documentations +* Responsibilities: Responsible for the quality of various project documents. diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 8a861859bfd..97481466538 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,31 +2,51 @@ layout: page title: Developer Guide --- +![Logo](images/Logo.png) +## Introduction + +PropertyMatch is a JavaFX application that helps property agents manage their database of customers and properties using a command-line interface. + +## Purpose of the Developer Guide + +This guide was made for developers looking to comprehend or build upon the functionalities of PropertyMatch, and software testers looking to test PropertyMatch’s features. + +## About the Guide + +This developer guide provides a high-level architecture overview of the workings of PropertyMatch, followed by main features and functions of PropertyMatch along with their explanations, design considerations and implementations + +## Table of Contents + * Table of Contents -{:toc} + {:toc} -------------------------------------------------------------------------------------------------------------------- -## **Acknowledgements** +## **1. Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +* Our developer guide is built upon [Addressbook-level3](https://se-education.org/addressbook-level3/DeveloperGuide.html) -------------------------------------------------------------------------------------------------------------------- -## **Setting up, getting started** +## **2. Setting up, getting started** Refer to the guide [_Setting up and getting started_](SettingUp.md). -------------------------------------------------------------------------------------------------------------------- -## **Design** +## **3. Design**
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +:bulb: **Tip:**
+- The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +- The diagrams are merely to showcase the general idea of how our codebase work. They do not serve as exact replicas of our code.
-### Architecture + + +### 3.1 Architecture +[Back to top](#table-of-contents) @@ -36,29 +56,29 @@ 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-W11-2/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2324S1-CS2103T-W11-2/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. The bulk of the app's work is done by the following four components: -* [**`UI`**](#ui-component): The UI of the App. -* [**`Logic`**](#logic-component): The command executor. -* [**`Model`**](#model-component): Holds the data of the App in memory. -* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. +* [**`UI`**](#32-ui-component): The UI of the App. +* [**`Logic`**](#33-logic-component): The command executor. +* [**`Model`**](#34-model-component): Holds the data of the App in memory. +* [**`Storage`**](#35-storage-component): Reads data from, and writes data to, the hard disk. -[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. +[**`Commons`**](#36-common-classes) represents a collection of classes used by multiple other components. **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delcust 1`. Each of the four main components (also shown in the diagram above), * defines its *API* in an `interface` with the same name as the Component. -* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point). For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. @@ -66,74 +86,79 @@ For example, the `Logic` component defines its API in the `Logic.java` interface The sections below give more details of each component. -### UI component +### 3.2 UI component +[Back to top](#table-of-contents) -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-W11-2/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`, `CustomerListPanel`, `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-W11-2/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2324S1-CS2103T-W11-2/tp/blob/master/src/main/resources/view/MainWindow.fxml) The `UI` component, * executes user commands using the `Logic` component. * listens for changes to `Model` data so that the UI can be updated with the modified data. * keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +* depends on some classes in the `Model` component, as it displays `Customer` object residing in the `Model`. -### Logic component +### 3.3 Logic component +[Back to top](#table-of-contents) -**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-W11-2/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: -The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example. +The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delcust 1")` API call as an example. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `delcust 1` Command](images/DeleteCustomerSequenceDiagram.png) -
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +
:information_source: **Note:** The lifeline for `DeleteCustomerCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
How the `Logic` component works: -1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to delete a person). -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCustomerCommandParser`) and uses it to parse the command. +2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCustomerCommand`) which is executed by the `LogicManager`. +3. The command can communicate with the `Model` when it is executed (e.g. to delete a customer). +4. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. +* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCustomerCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCustomerCommand`) which the `AddressBookParser` returns back as a `Command` object. +* All `XYZCommandParser` classes (e.g., `AddCustomerCommandParser`, `DeleteCustomerCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. + +### 3.4 Model component +[Back to top](#table-of-contents) -### 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-W11-2/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 currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the address book data i.e., all `Customer` objects (which are contained in a `UniqueCustomerList` object). +* stores the currently 'selected' `Customer` 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.
+
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Customer` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Customer` needing their own `Tag` objects.
-### Storage component +### 3.5 Storage component +[Back to top](#table-of-contents) **API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) @@ -144,100 +169,304 @@ The `Storage` component, * 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`) -### Common classes +### 3.6 Common classes Classes used by multiple components are in the `seedu.addressbook.commons` package. -------------------------------------------------------------------------------------------------------------------- -## **Implementation** +## 4. **Implementation** This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### 4.1 Internal implementations of Customer and Property +[Back to top](#table-of-contents) -#### Proposed Implementation +#### 4.1.1 Customer +The structure of a `Customer` object can be viewed in the object diagram below. -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: +![CustomerObjectDiagram](images/CustomerObjectDiagram.png) -* `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. +From the diagram, the `Customer` object consists of the following attributes. +- `Name`: The name of the customer. +- `Phone`: The phone number of the customer. +- `Email`: The email address of the customer. +- `Budget`: The budget that the customer is willing to pay for the property. +- `Tags`: The characteristics of the property that the customer is looking for. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +#### 4.1.2 Property +The structure of a `Property` object can be viewed in the object diagram below. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +![PropertyObjectDiagram](images/PropertyObjectDiagram.png) -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. +From the diagram, the `Property` object consists of the following attributes. +- `PropName`: The name of the property. +- `PropAddress`: The address of the property. +- `PropPhone`: The phone number of the property. +- `Price`: The price of the property. +- `Tags`: The characteristics of the property that the customer is looking for. -![UndoRedoState0](images/UndoRedoState0.png) +#### 4.1.3 User input validation +Phone numbers both follow this validation regex. +``` +public static final String VALIDATION_REGEX = "(6|8|9)\\d{7}"; +``` +This means that phone numbers must start with 6, 8, or 9 and must have at least 8 digits. This means it only supports Singapore phone numbers. -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. +
-![UndoRedoState1](images/UndoRedoState1.png) +`Price` and `Budget` follow this validation regex. +``` +public static final String VALIDATION_REGEX = "[1-9]\\d{4,11}"; +``` +This means that the price or budget must be a positive integer with at least 5 digits and at most 12 digits. It also cannot start with 0. -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`. +We decided on this arbitrary limit because we thought that it is not possible for a property to cost less than 10,000 dollars. We also thought that it is not possible for a property to cost more than 1 trillion dollars. -![UndoRedoState2](images/UndoRedoState2.png) +### 4.2 Adding of customers and properties +[Back to top](#table-of-contents) -
: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`. +#### 4.2.1 Customers +The customer list should not allow duplicate customers to be added. This is because it is not possible for a customer to have two profiles. This means we need to define a condition to check if the customer is already in the list. -
+At the start, we used a customer's `Name` to check if he was a duplicate entry. However, we realised that two unique customers may have the same names. Therefore, we decided to use the customer's `Phone` number to check if he was a duplicate entry. This is because it is unlikely for two unique customers to have the same phone number. -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +The activity diagram for the creation of a customer can be seen below. +![AddCustomerActivityDiagram](images/AddCustomerActivityDiagram.png) -![UndoRedoState3](images/UndoRedoState3.png) +#### 4.2.2 Properties +Similar to the customer list, the property list should not allow duplicate properties to be added as well. This is because it will cause bugs in our other features. This means we need to define a condition to check if the property is already in the list. -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. +At the start, we used a property's `Name` to check if he was a duplicate entry. However, we realised that two unique properties may have the same names. Therefore, we decided to use the property's `Address` to check if he was a duplicate entry. This is because it is unlikely for two unique properties to have the same address. -
+The activity diagram for the creation of a property can be seen below. +![AddPropertyActivityDiagram](images/AddPropertyActivityDiagram.png) -The following sequence diagram shows how the undo operation works: +### 4.3 Editing of buyers and properties +[Back to top](#table-of-contents) -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +#### 4.3.1 Motivation +The property agent may want to edit the details of a customer or property after adding it to the application. For example, the property agent may want to change the budget range of a customer after adding it to PropertyMatch. +Or, the property agent may want to change the budget of a property after adding it to PropertyMatch. -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +#### 4.3.2 Implementation +The `EditCustomerCommand` and `EditPropertyCommand` classes extends the `Command` class. They are used to edit the details of a customer or property, respectively. +Both commands allow the user to change any of the fields of a customer or property. The commands expect at least one flag to be edited, otherwise an error message will be displayed. +When the edit command is inputted, the `EditCustomerCommandParser` and `EditPropertyCommandParser` classes are used to parse the user input and create the respective `EditCustomerCommand` and `EditPropertyCommand` objects. +When these created command objects are executed by the `LogicManager`, the `EditCustomerCommand#execute(Model model)` or `EditPropertyCommand#execute(Model model)` methods are called. These methods will edit the customer or property in the model, and return a `CommandResult` object. +
:exclamation: **Note:** +To be more concise, we will be referring to both customers and properties as entities in this section from here onwards.
-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. +During this execution process, the existing entity is first retrieved from the model. The fields of the entities are then edited according to what flags were passed in by the user during the edit commands. +A new customer or property is then created with the edited fields, and any fields that have not been edited will be copied over from the original entity. The new entity is then added to the model, and the original entity is removed from the model. +The new customer or property is then added into the model, replacing the old one. The new entity will then be displayed to the user, and a success message is displayed. -
: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. +#### 4.3.3 Design Considerations +**Aspect: How the edit commands should relate to each other:** -
+* **Alternative 1 (current choice):** `EditCustomerCommand` and `EditPropertyCommand` are separate, and both inherit from the `Command` class. + * Pros: + * Both the `Customer` and `Property` classes have different fields that are exclusive to each other. + * This reduces complexity of the system, and unexpected behaviours. + * The inheritance of the `Command` class allows us to keep to the Command design pattern, to easily add more types of edit commands in the future, without having to change the existing code. + * Cons: + * More boilerplate code for each of the classes, which increases the size of the codebase. +* **Alternative 2:** A single `EditCommand` class is used to edit both customer and property. + * Cons: + * Unnecessary complexity is introduced into the system. + +**Aspect: How the edited entities should interact with the model:** +* We also decided for the edit commands to create a new entity, instead of editing the existing one. This allows us to not include any setters in the `Customer` and `Property` classes, which make the objects immutable, so there is less likelihood of unexpected changes to the object. This enables us to maintain the defensiveness of our code. + By creating a new entity every time the property agent edits, we can easily add the new customer or property into the model, and remove the old one. This also allows us to easily undo the edit command in the future, by simply adding the old entity back into the model. + +The following sequence diagram shows how the `EditCustomerCommand` is executed. + +![EditCustomerSequenceDiagram](images/EditCustomerSequenceDiagram.png) + +The following sequence diagram shows how the `EditPropertyCommand` is executed. + +![EditPropertySequenceDiagram](images/EditPropertySequenceDiagram.png) +### 4.4 Deleting of customers and properties +[Back to top](#table-of-contents) + +#### 4.4.1 Motivation +The property agent may want to delete the profile of any customer or property that has been previously added into the app. For example, the property agent might want to remove a particular property after it has been sold. +Or, a particular client is no longer interested in buying a house anymore. + +#### 4.4.2 Implementation +The `DeleteCustomerCommand` and `DeletePropertyComannd` classes extends from the `Command` class. They are used to delete the details of a Customer or Property respectively. The command expects exactly one `INDEX` of the Customer or Property to be deleted, otherwise an error message will be displayed. +When the delete command is inputted, the `DeleteCustomerCommandParser` and `DeletePropertyCommandParser` classes are used to parse the user input and create the `DeleteCustomerCommand` or `DeletePropertyCommand` objects respectively. + +When these created command objects are executed by the `LogicManager`, the `DeleteCustomerCommand#execute(Model model)` or `DeletePropertyCommand#execute(Model model)` methods are called. These methods will delete the customer or property in the model, and return a `CommandResult` object. + +#### 4.4.3 Design Considerations +**Aspect: How the delete commands should relate to each other:** + +* **Alternative 1 (current choice):** `DeleteCustomerCommand` and `DeletePropertyCommand` are separate, and both inherit from the `Command` class. + + +* **Alternative 2:** A single `DeleteCommand` class is used to edit both customer and property. + * Cons: + * Unnecessary complexity is introduced into the system. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +The following activity diagram summarises the execution of a `delcust INDEX` command. -![UndoRedoState4](images/UndoRedoState4.png) +![DeleteCustomerActivityDiagram](images/DeleteCustomerActivityDiagram.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. +The following sequence diagram shows how the `DeleteCustomerCommand` is executed. -![UndoRedoState5](images/UndoRedoState5.png) +![DeleteCustomerSequenceDiagram](images/DeleteCustomerSequenceDiagram.png) -The following activity diagram summarizes what happens when a user executes a new command: +The following sequence diagram shows how the `DeletePropertyCommand` is executed. - +![DeletePropertySequenceDiagram](images/DeletePropertySequenceDiagram.png) -#### Design considerations: +### 4.5 Finding of Customers and Properties +[Back to top](#table-of-contents) -**Aspect: How undo & redo executes:** +#### 4.5.1 Motivation +The property agent may want to find and access the details of a particular Customer or Property that has been previously added into the app. For example, the property agent may want to refresh their memory on a particular customer's budget. Or the property agent may want to check the details of a particular property. -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +#### 4.5.2 Implementation +The `FindCustomerCommand` and `FindPropertyCommand` classes extends the `Command` class. They are used to find the profiles of a customer or property, respectively. +Both commands allow the user to find any customer or property. The commands expect at least one substring to base the search on, otherwise an error message will be displayed. -* **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. +When the find command is inputted, the `FindCustomerCommandParser` and `FindPropertyCommandParser` classes are used to parse the user input and create the respective `FindCustomerCommand` and `FindPropertyCommand` objects. +When these created command objects are executed by the `LogicManager`, the `FindCustomerCommand#execute(Model model)` or `FindPropertyCommand#execute(Model model)` methods are called. These methods will find the customer or property in the model, and return a `CommandResult` object. -_{more aspects and alternatives to be added}_ +The following sequence diagram shows how the `FindCustomerCommand` is executed. +![FindCustomerSequenceDiagram](images/FindCustomerSequenceDiagram.png) -### \[Proposed\] Data archiving +#### 4.5.3 Design Considerations +**Aspect: How the find commands should relate to each other:** -_{Explain here how the data archiving feature will be implemented}_ +* **Alternative 1 (current choice):** `FindCustomerCommand` and `FindPropertyCommand` are separate, and both inherit from the `Command` class. + * Pros: + * Both the `Customer` and `Property` classes have different fields that are exclusive to each other. + * This reduces complexity of the system, and unexpected behaviours. + * The inheritance of the `Command` class allows us to keep to the Command design pattern, to easily add more types of edit commands in the future, without having to change the existing code. + * Cons: + * More boilerplate code for each of the classes, which increases the size of the codebase. +* **Alternative 2:** A single `FindCommand` class is used to find both customer and property. + * Cons: + * Unnecessary complexity is introduced into the system. +### 4.6 Filtering of Customers and Properties +[Back to top](#table-of-contents) + +#### 4.6.1 Motivation +The property agent may want to see a list of customers based on their budget. For example, the property agent may want to filter customers with budget more than $100000. +Or, the property agent may want to see a list of customers based on the characteristics of the property they desired. For example, the property agent may want to filter customers who love pink properties. +Or, the property agent may want to see a list of customers based on both budget and characteristics to enhance productivity. + +The property agent may also want to see a list of properties based on their budget. For example, the property agent may want to filter properties with budget less than $1000000. +Or, the property agent may want to see a list of properties based on the characteristics. For example, the property agent may want to filter pink properties. +Or, the property agent may want to see a list of properties based on both budget and characteristics to enhance productivity. + +#### 4.6.2 Implementation +The `FilterCustomerCommand` and `FilterPropertyCommand` class extends the `Command` class. They are used to filter customers and properties, respectively. +The `filtercust` command allows the user to filter customers based on their budget and/or properties' characteristics they love. While the `filterprop` command allows the user to filter properties based on their budget and/or characteristics. +The commands expect at least one flag, either budget/price or characteristics, to be used as a filter. +When the filter command is inputted, the `FilterCustomerCommandParser` and `FilterPropertyCommandParser` class is used to parse the user input and create the respective `FilterCustomerCommand` or `FilterPropertyCommand` objects. +When these created command objects are executed by the `LogicManager`, the `FilterCustomerCommand#execute(Model model)` or `FilterCustomerCommand#execute(Model model)` methods are called. These methods will update the filtered customer or property list in the `model` which will eventually update the customers shown in the UI, and return a `CommandResult` object. + +During this execution process, a new `BudgetAndTagsInRangePredicate` or `PriceAndTagsInRangePredicate` object which is used as a predicate to check whether a customer's budget is bigger and all the characteristics are desired by the customer or whether a property's price is lower and the property has all the characteristics, respectively. +All customers or properties will be tested using this `BudgetAndTagsInRangePredicate` or `PriceAndTagsInRangePredicate`. Customers or properties which satisfy this condition will be included into the `FilteredCustomerList` or `FilteredPropertyList` in the model. + +The following sequence diagram shows how the `FilterCustomerCommand` is executed. +![FilterCustomerSequenceDiagram](images/FilterCustomerSequenceDiagram.png) + +#### 4.6.3 Design Considerations +**Aspect: How the filter customer commands should relate to filter property commands:** + +* **Alternative 1 (current choice):** `FilterCustomerCommand` and `FilterPropertyCommand` inherit from the `Command` class and separated with each other. + * Pros: + * Both the `Customer` and `Property` classes have different fields that are exclusive to each other. + * This reduces complexity of the system, and unexpected behaviours. + * The inheritance of the `Command` class allows us to keep to the Command design pattern, to easily add more types of edit commands in the future, without having to change the existing code. + * Cons: + * More boilerplate code for each of the classes, which increases the size of the codebase. +* **Alternative 2:** A single `FilterCommand` class is used to edit both customer and property. + * Cons: + * Unnecessary complexity is introduced into the system. + +**Aspect: How the filtered customers or properties should interact with the model:** +* We also decided for the filter commands to put the filtered customers or filtered properties in a different list (`FilteredCustomerList` and `FilteredPropertyList`), instead of removing the 'unused' customers and properties from the model. + +### 4.7 Matching for Customers and Properties +[Back to top](#table-of-contents) + +#### 4.7.1 Motivation +The property agent may want to get all properties that satisfy the customers criteria or get all customers that want the properties characteristics. For example, the property agent want to get all properties that certain specified customer want and can afford. +Or, the property agent want to get all customers that might want to buy certain specified property. + +#### 4.7.2 Implementation +The `MatchCustomerCommand` and `MatchPropertyCommand` classes extends the `Command` class. +They are used to match the details of a customer or property, respectively. +The command expects exactly one "Index" of the customer or property to be match, otherwise an error message will be displayed. +When the match command is inputted, the `MatchCustomerCommandParser` or `MatchPropertyCommandParser` classes are used to parse the user input and create the `MatchCustomerCommand` or `MatchPropertyCommand` objects respectively. +When these created command objects are executed by the `LogicManager`, the `MatchCustomerCommand#execute(Model model)` or `MatchProeprtyCommand#execute(Model model)` method is called. +These methods will match the customer or property in the model, and return a `ComandResult` object. + +The following sequence diagram shows how the `MatchCustomerCommand` is executed. +![MatchCustomerSequenceDiagram](images/MatchCustomerSequenceDiagram.png) + +#### 4.7.3 Design Consideration + +* *Alternative 1 (current choice):* `MatchPropertyCommand` and `MatchCustomerCommand` are separate, and both inherit from the `Command` class. + * Pros: + * Allows the property agent to match only customers or properties. + * The inheritance of the `Command` class allows us to keep to the Command design pattern, to easily add more types of edit commands in the future, without having to change the existing code. + * Cons: + * More code for each of the classes, which increases the size of the codebase + * More commands for the property agent to remember +* *Alternative 2:* A single `MatchCommand` class is used to match both customers and properties. + * Pros: + * Lesser commands for the property agent to remember + * Cons: + * Unable to match only customers or properties without specify it which increase the complexity of the commands + +### 4.8 Reset the application with `clear` +[Back to top](#table-of-contents) + +#### 4.8.1 Motivation +The property agent may want to clear the list of customers or properties to start from a clean slate. + +#### 4.8.2 Implementation +The `ClearCommand` extends the `Command` class. It is used to clear the list of customers or properties. + +#### 4.8.3 Design Considerations + +* **Alternative 1:** `ClearPropertyCommand` and `ClearCustomerCommand` are separate, and both inherit from the `Command` class. + * Pros: + * Allows the property agent to clear only customers or properties. + * The inheritance of the `Command` class allows us to keep to the Command design pattern, to easily add more types of edit commands in the future, without having to change the existing code. + * Cons: + * More boilerplate code for each of the classes, which increases the size of the codebase + * More commands for the property agent to remember +* **Alternative 2 (current choice):** A single `clear` class is used to clear both customers and properties. + * Pros: + * Less overhead to deal with + * Lesser commands for the property agent to remember + * Cons: + * Unable to clear only customers or properties + +### 4.9 Exit with a delay +[Back to top](#table-of-contents) + +#### 4.9.1 Motivation +The property agent should exit the application with a peace of mind. A delay is required for the property agent to read the exit message before the application closes. + +#### 4.9.2 Implementation +The `ExitCommand` extends the `Command` class. It is used to close the application and display the goodbye message. +The following code is used to implement the delay. There are many ways to implement a timeout, but since we are using JavaFX. This is probably the simplest way to accomplish it. +``` +PauseTransition delay = new PauseTransition(Duration.seconds(3)); +delay.setOnFinished(e -> primaryStage.hide()); +delay.play(); +``` -------------------------------------------------------------------------------------------------------------------- @@ -251,81 +480,240 @@ _{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Requirements** +## **Appendix A: Requirements** -### Product scope +### A.1 Product scope **Target user profile**: -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +- Property agents new to the real estate industry in Singapore +- Looking for a customer-property management tool -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: +This tool is for property agents who want to organise their client profiles with their corresponding properties. Property agents can boost their efficiency by seamlessly matching their clients with their desired properties. -### User stories +### A.2 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 | +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|------------------------------------------------------------------|--------------------------------------------------------------------------|-------------------------------------------------------------------------------| +| `* * *` | property agent | add a new customer's details | save their details in PropertyMatch | +| `* * *` | property agent | add a new property's details | save their details in PropertyMatch | +| `* * *` | property agent | view all customers | easily access all my customers | +| `* * *` | property agent | view all properties | easily access all available properties | +| `* * *` | property agent | remove existing customers | remove customer who is either not interested anymore or has bought a property | +| `* * *` | property agent | remove existing properties | remove property which is either already sold or not sold anymore | +| `* * *` | property agent | read the user guide | learn to use the application | +| `* *` | property agent | update customers' details | ensure customers' information is up to date | +| `* *` | property agent | update properties' details | ensure properties' information is up to date | +| `* *` | property agent with a lot of customers | find customers based on customers' names | get a list of customers I am looking for | +| `* *` | property agent with a lot of properties | find properties based on properties' names | get a list of properties I am looking for | +| `* *` | property agent with a lot of customers | filter customers based on customers' budget or required characteristics | understand specific needs of my customers | +| `* *` | property agent with a lot of properties | filter properties based on properties' price or required characteristics | understand the specific traits of my properties | +| `* *` | property agent with a lot of customers | match a customer with suitable existing properties | get a list of specific properties with the detail satisfy the customer | +| `* *` | property agent with a lot of properties | match a property with suitable existing customers | get a list of specific customers with the detail satisfy the property | +| `*` | property agent | add notes to customers' profiles | streamline customer management profile | +| `*` | property agent | add notes to properties' profiles | streamline customer property profile | +| `*` | experienced property agent using the application with new device | import and export customers' data | transfer customers' data across devices | +| `*` | experienced property agent using the application with new device | import and export properties' data | transfer properties' data across devices | + + +### A.3 Use cases + +**Use Case: UC01 - Add customer** + +System: PropertyMatch address book + +Actor: Property Agent + +1. Property agent fills in name, phone number, email, budget and desired characteristic (tags). +2. Property agent adds customer to address book.
+Use case ends. + +Extension:
+1a. PropertyMatch detects an error in the entered command.
+ 1a1. User edits the command.
+ Step 1a1 is repeated until the command entered is correct. + +**Use Case: UC02 - Add property** + +System: PropertyMatch address book + +Actor: Property Agent + +1. Property agent fills in name, address, characteristics (tags), phone number, budget of property. +2. Property agent adds property to address book.
+Use case ends. + +Extension:
+1a. PropertyMatch detects an error in the entered command.
+ 1a1. User edits the command.
+ Step 1a1 is repeated until the command entered is correct. + +**Use Case: UC03 - Delete customer** + +System: PropertyMatch address book + +Actor: Property Agent + +1. Property agent identifies the customer to be deleted and retrieves its index. +2. Property agent inserts the index into the CLI. +3. Property agent deletes the customer details from the address book.
+Use case ends. -*{More to be added}* +Extension:
+2a. PropertyMatch detects an invalid index.
+ 2a1. User edits the index.
+ Step 2a1 is repeated until the index entered is correct. -### Use cases +**Use Case: UC04 - Delete property** -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +System: PropertyMatch address book -**Use case: Delete a person** +Actor: Property Agent -**MSS** +1. Property agent identifies the property to be deleted and retrieves its index. +2. Property agent inserts the index into the CLI. +3. Property agent deletes the property details from the address book.
+Use case ends. -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 +Extension:
+2a. PropertyMatch detects an invalid index.
+ 2a1. User edits the index.
+ Step 2a1 is repeated until the index entered is correct. - Use case ends. +**Use Case: UC05 - List all entities** -**Extensions** +System: PropertyMatch address book -* 2a. The list is empty. +Actor: Property Agent - Use case ends. +1. Property agent decides to list either properties or customers. +2. Property agent inputs the command. +3. Property agent is able to view all entities.
+Use case ends. -* 3a. The given index is invalid. +Extension:
+2a. PropertyMatch detects an error in the entered command.
+ 2a1. User edits the command.
+ Step 2a1 is repeated until the command entered is correct. - * 3a1. AddressBook shows an error message. +**Use Case: UC06 - Edit entities** - Use case resumes at step 2. +System: PropertyMatch address book -*{More to be added}* +Actor: Property Agent -### Non-Functional Requirements +1. Property agent decides to edit either properties or customers. +2. Property agent identifies the property or customer to be deleted and retrieves its index. +3. Property agent edits entity’s respective fields. +4. Property agent is able to edit the entities details in the address book.
+Use case ends. -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. +Extension:
+3a. PropertyMatch detects an error in the entered command.
+ 3a1. User edits the command.
+ Step 3a1 is repeated until the command entered is correct. -*{More to be added}* +**Use Case: UC07 - Find specific entity** -### Glossary +System: PropertyMatch address book -* **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others +Actor: Property Agent + +1. Property agent identifies the specific entity he wants. +2. Property agent enters the entity name he wants. +3. Property agent views the entity.
+Use case ends. + +Extension:
+2a. PropertyMatch detects an invalid name entered.
+ 2a1. User edits the inputted name.
+ Step 2a1 is repeated until a valid name is entered. + +**Use Case: UC08 - Filter properties or customer** + +System: PropertyMatch address book + +Actor: Property Agent + +1. Property agent identifies the specific criteria of properties or customer he wants. +2. Property agent enters the criteria he wants. +3. Property agent views the entity with that fulfills his criteria.
+Use case ends. + +Extension:
+2a. PropertyMatch detects an error with the criteria entered.
+ 2a1. User edits the inputted criteria.
+ Step 2a1 is repeated until a valid criteria is entered. + +**Use Case: UC09 - Match properties to customers** + +System: PropertyMatch address book + +Actor: Property Agent + +1. Property agent identifies the customer to be matched to its corresponding properties. +2. Property agent inserts the index into the CLI. +3. Property agent gets a list of properties that matches the criteria of the customer.
+Use case ends. + +Extension:
+2a. PropertyMatch detects an invalid index.
+ 2a1. User edits the index.
+ Step 2a1 is repeated until the index entered is correct. + +**Use Case: UC10 - Match customers to properties** + +System: PropertyMatch address book + +Actor: Property Agent + +1. Property agent identifies the property to be matched to its corresponding customer. +2. Property agent inserts the index into the CLI. +3. Property agent gets a list of customers with criteria is fulfilled by the property.
+Use case ends. + +Extension:
+2a. PropertyMatch detects an invalid index.
+ 2a1. User edits the index.
+ Step 2a1 is repeated until the index entered is correct. + +**Use Case: UC11 - Import and export data** + +System: PropertyMatch address book + +Actor: Property Agent + +1. Property agent can import data from another application to the existing application. +2. Property agent can export data from the application and save the data.
+Use case ends. + +### A.4 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 properties and clients 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. +4. The system should respond to user inputs within 5 seconds. +5. The user interface should be intuitive enough for property agents who are not IT-savvy. +6. App functionality should not require access to an internet connection. +7. Should save data locally in a human editable file. +8. Should have a graphical user interface with readable font of at least size 11. +9. Should have a graphical user interface which is intuitive and user-friendly. + +### A.5 Glossary + +* **Mainstream OS** : Windows, Linux, Unix, OS-X +* **API** : API stands for Application Programming Interface, which is a set of definitions and protocols for building and integrating application software. +* **Tag** : A label or keyword assigned to a customer or property in the address book, to categorize, annotate, or identify specific characteristics of that customer or property. +* **CLI** : Command-line interface (CLI) is a text-based user interface (UI) used to run programs, manage computer files and interact with the computer. +* **GUI** : A graphical user interface (GUI) is a digital interface in which a user interacts with graphical components such as icons, buttons, and menus. -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Instructions for manual testing** +## **Appendix B: Instructions for manual testing** Given below are instructions to test the app manually. @@ -334,44 +722,321 @@ testers are expected to do more *exploratory* testing.
-### Launch and shutdown +### B.1 Launch and shutdown 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. + 2. Double-click the jar file. If nothing happens after double-clicking the jar file, run `java -jar propertymatch.jar` on the command line in the folder.
+ **Expected**: Shows the GUI with a set of sample contacts. The window size may not be optimum. + +2. Saving window preferences + + 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + + 2. Re-launch the app by double-clicking the jar file.
+ **Expected**: The most recent window size and location is retained. + + +### B.2 Adding a customer + +1. Adding a customer with only compulsory fields + 1. **Test case**: `addcust n/Tim Cook p/91234567 e/cook@apple.com b/2500000` + 2. **Expected**: New customer should be added to Customer List with relevant details. +2. Adding a customer with all fields + 1. **Test case**: `addcust n/Phoebe p/87654321 e/pb@gmail.com b/200000 c/bright c/sunny c/white` + 2. **Expected**: New customer should be added to Customer List with relevant details. +3. Adding a customer with missing fields + 1. **Test case**: `addcust n/Jack p/91135555` + 2. **Expected**: Command entered is highlighted in red. "Invalid command format" error message should be displayed and information regarding the syntax of the `addcust` command should be shown. +4. Adding a customer with an invalid field + 1. **Test case**: `addcust n/Jack p/51234567 e/cook@apple.com b/2500000` + 2. **Expected**: Command entered is highlighted in red. Error message is shown to highlight the invalid field to tell the user what is wrong. + +### B.3 Adding a property + +1. Adding a property with only compulsory fields + 1. **Test case**: `addprop n/Aqua Heights a/195 Paya Lebar 3 #18-32 p/91135235 pr/700000` + 2. **Expected**: New property should be added to Property List with relevant details. +2. Adding a property with all fields + 1. **Test case**: `addprop n/Skyview a/214 Clementi Ave 2 #09-78 p/98835235 pr/500000 c/bright c/sunny c/big c/square` + 2. **Expected**: New customer should be added to Property List with relevant details. +3. Adding a property with missing fields + 1. **Test case**: `addprop n/Beta p/91135555` + 2. **Expected**: Command entered is highlighted in red. "Invalid command format" error message should be displayed and information regarding the syntax of the `addprop` command should be shown. +4. Adding a property with an invalid field + 1. **Test case**: `addprop n/Beta p/51234567 a/195 Paya Lebar 4 pr/700000` + 2. **Expected**: Command entered is highlighted in red. Error message is shown to highlight the invalid field to tell the user what is wrong. + +### B.4 Listing all customers + +**Prerequisite**: Customer List should be a subset of the original list + +1. View all customers in database + 1. **Test case**: `listcust` + 2. **Expected**: Customer List should be shown with all customers in the database. "Listed all customers" should be displayed. +2. Adding parameters to the command + 1. **Test case**: `listcust 1`, `listcust p` + 2. **Expected**: Same behaviour as above. + +### B.5 Listing all properties + +**Prerequisite**: Property List should be a subset of the original list + +1. View all properties in database + 1. **Test case**: `listprop` + 2. **Expected**: Property List should be shown with all properties in the database. "Listed all properties" should be displayed. +2. Adding parameters to the command + 1. **Test case**: `listprop 1`, `listprop p` + 2. **Expected**: Same behaviour as above. + +### B.6 Deleting a customer + +**Prerequisite**: Customer List should have at least 1 customer record. + +1. Deleting a customer with a valid index + 1. **Test case**: `delcust 1` + 2. **Expected**: First customer is deleted from the list. Details of the deleted customer is displayed. +2. Deleting a customer with an invalid index + 1. **Test case**: `delcust 0` + 2. **Expected**: No customer is deleted. Error details shown in the status message. + 3. **Test case**: `delcust x`, where `x` is larger than the total customer count + 4. **Expected**: No customer is deleted. Error details shown in the status message. +3. Deleting a customer with no index provided + 1. **Test case**: `delcust` + 2. **Expected**: No customer is deleted. Error details shown in the status message. + +### B.7 Deleting a property + +**Prerequisite**: Property List should have at least 1 property record. -1. Saving window preferences +1. Deleting a property with a valid index + 1. **Test case**: `delprop 1` + 2. **Expected**: First property is deleted from the list. Details of the deleted property is displayed. +2. Deleting a property with an invalid index + 1. **Test case**: `delprop 0` + 2. **Expected**: No property is deleted. Error details shown in the status message. + 3. **Test case**: `delprop x`, where `x` is larger than the total property count + 4. **Expected**: No property is deleted. Error details shown in the status message. +3. Deleting a property with no index provided + 1. **Test case**: `delprop` + 2. **Expected**: No property is deleted. Error details shown in the status message. + +### B.8 Editing a customer + +**Prerequisite**: Customer List should have at least 1 customer record. + +1. Editing the first customer with valid index and valid fields + 1. **Test case**: `editcust 1 n/Andrew e/andrew@gmail.com` + 2. **Expected**: The name and email fields of the first customer should be updated. +2. Attempting to edit with an invalid index + 1. **Test case**: `editcust 0 n/Andrew` + 2. **Expected**: Command is highlighted red and error message will be displayed. +3. Attempting to edit a customer with valid index but without valid fields + 1. **Test case**: `editcust 1` + 2. **Expected**: Command is highlighted red and error message will be displayed. + +### B.9 Editing a property + +**Prerequisite**: Property List should have at least 1 property record. + +1. Editing the first property with valid index and valid fields + 1. **Test case**: `editprop 1 n/Asiatique p/98765432` + 2. **Expected**: The name and phone number fields of the first property should be updated. +2. Attempting to edit with an invalid index + 1. **Test case**: `editprop 0 n/Asiatique` + 2. **Expected**: Command is highlighted red and error message will be displayed. +3. Attempting to edit a property with valid index but without valid fields + 1. **Test case**: `editprop 1` + 2. **Expected**: Command is highlighted red and error message will be displayed. + +### B.10 Finding customers + +**Prerequisite**: A customer that has 'Tim' in his name must exist in the customer list. + +1. Finding customers with "Tim" substring in their name + 1. **Test case**: `findcust Tim` + 2. **Expected**: Customer List should be filtered to contain only customers that start with the "Tim" substring in their name. (e.g. Timothy, Lee Timmy, Tim Cook) It should be case-insensitive. "x customers listed" should be displayed, where x refers to the number of customers in the new filtered list. +2. Attempting to find a customer with no substring + 1. **Test case**: `findcust` + 2. **Expected**: Command is highlighted red and error message will be displayed. + +### B.11 Finding properties + +**Prerequisite**: A property that has 'Sky' in his name must exist in the property list. + +1. Finding properties with "Sky" substring in their name + 1. **Test case**: `findprop Sky` + 2. **Expected**: Property List should be filtered to contain only properties that start with the "Sky" substring in their name. (e.g. Skyvista, The Skyrim, Sky View) It should be case-insensitive. "x properties listed" should be displayed, where x refers to the number of properties in the new filtered list. +2. Attempting to find a property with no substring + 1. **Test case**: `findprop` + 2. **Expected**: Command is highlighted red and error message will be displayed. + +### B.12 Filtering customers + +**Prerequisite**: A customer that has a budget of 100,000 and has 'bright' in his characteristics must exist in the customer list. + +1. Filter customers with valid budget and characteristics + 1. **Test case**: `filtercust b/100000 c/bright` + 2. **Expected**: Customer List should be filtered to contain only customers that have budgets of at least 100000 or more and has at least one tag named "bright". +2. Invalid filter formats + 1. **Test case**: `filtercust`, `filtercust 1` + 2. **Expected**: Command is highlighted red and error message will be displayed. + +### B.13 Filtering properties + +**Prerequisite**: A property that has a price of 500,000 and has 'big' in his characteristics must exist in the price list. + +1. Filter properties with valid price and characteristics + 1. **Test case**: `filterprop pr/500000 c/big` + 2. **Expected**: Property List should be filtered to contain only properties that have prices of 500000 or less and has at least one tag named "big". +2. Invalid filter formats + 1. **Test case**: `filterprop`, `filterprop 1` + 2. **Expected**: Command is highlighted red and error message will be displayed. + +### B.14 Matching properties to a customer + +**Prerequisite**: At least 1 customer record. + +1. Match properties which are suitable for a customer + 1. **Test case**: `matchcust 1` + 2. **Expected**: Properties with prices less than or equal to the budget of customer 1 will be shown. If the customer has existing tags, only properties with at least 1 matching tag will be shown. If the customer has no tag, only the budget requirement needs to be met. +2. Invalid match formats + 1. **Test case**: `matchcust`, `matchcust 0` + 2. **Expected**: Command is highlighted red and error message will be displayed. - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. +### B.15 Matching customers to a property + +**Prerequisite**: At least 1 property record. + +1. Match customers which are suitable for a property + 1. **Test case**: `matchprop 1` + 2. **Expected**: Customers with budgets more than or equal to the price of property 1 will be shown. If the property has existing tags, only customers with at least 1 matching tag will be shown. If the property has no tag, only the price requirement needs to be met. +2. Invalid match formats + 1. **Test case**: `matchprop`, `matchprop 0` + 2. **Expected**: Command is highlighted red and error message will be displayed. + +### B.16 Clearing the application - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. +1. All data should be cleared + 1. **Test case**: `clear` + 2. **Expected**: All data from customer list and property list should be cleared. +2. Extra parameters added + 1. **Test case**: `clear 123`, `clear hello` + 2. **Expected**: All data from customer list and property list should be cleared. -1. _{ more test cases …​ }_ +### B.17 Viewing help + +1. Help window should be cleared + 1. **Test case**: `help` + 2. **Expected**: Window containing PropertyMatch's user guide should be shown. +2. Extra parameters added + 1. **Test case**: `help 123`, `help hello` + 2. **Expected**: Window containing PropertyMatch's user guide should be shown. -### Deleting a person +### B.18 Exiting the application + +1. Application should exit after 3 seconds + 1. **Test case**: `exit` + 2. **Expected**: Application should exit after 3 seconds. +2. Extra parameters added + 1. **Test case**: `exit 123`, `exit hello` + 2. **Expected**: Application should exit after 3 seconds. + +-------------------------------------------------------------------------------------------------------------------- + +## **Appendix C: Proposed enhancements** + +### C.1 Improved GUI + +#### C.1.1 Motivation +Our current GUI is not as user-friendly as we would like it to be. A well-designed and aesthetic GUI provides a more intuitive and user-friendly interface, making it easier for users to navigate, access features, and perform tasks within the app. It can also lead to higher user satisfaction. + +#### C.1.2 Implementation +1. Update the color scheme to make the GUI more visually appealing. Use colors that resonate with our brand or that create a pleasant atmosphere. +2. Integrate relevant icons and imagery to represent different features and functions within the app, providing visual cues for users. +3. Add whitespace effectively to create a clean and organized layout and reduce visual clutter. +4. Add user guidance. Provide tooltips, hints, or tutorials for new users to help them get started and understand the app's features. + +### C.2 Improved Error Messages + +#### C.2.1 Motivation +Some of our current error messages can be quite vague. As a user, this can be hard for them to decipher what is wrong. Upon receiving the error message, they may not know what is wrong. Knowing what they entered wrongly can lead to higher user satisfaction. + +#### C.2.2 Implementation +1. Update the error messages to be more specific. For example, if the user enters an invalid command, the error message should tell the user which part of the command is invalid, instead of just saying that the command is invalid. +2. Allow the `INDEX` for commands to accept very large numbers. Currently, the computer is unable to parse the number because it is too large, and PropertyMatch will mention that the command is invalid instead of mentioning that the number is too large. + +### C.3 Importing client data + +#### C.3.1 Motivation +We realise that many property agents already have existing customers and properties. They may not want to manually enter all the data into the application. This feature will allow them to import their existing data into the application, saving them time and resources. + +#### C.3.2 Implementation +1. Add an `Import` button to the menu bar. +2. Create a file selection mechanism in which the user can import data from CSV files. +3. Parse and process the data in the file. +4. Validate the imported data to ensure it meets expected standards. +5. Add the data fields to PropertyMatch. +6. Provide clear feedback to the user about success/failure (success message or error message). +7. Implement error handling, logging, and security measures. + +### C.4 Exporting client data + +#### C.4.1 Motivation +We realise that many property agents may want to export their existing data in PropertyMatch to other platforms. This feature will allow them to easily export their data into other platforms to perform other functions (such as data analysis) not supported on PropertyMatch. + +#### C.4.2 Implementation +1. Add an `Export` button to the menu bar. +2. Create a file selection mechanism in which the user can import data from CSV files. +3. Convert the data in the json files to CSV file. +4. Allow the user to name the CSV file. +5. Provide clear feedback to the user about success/failure (success message or error message). +6. Implement error handling, logging, and security measures. + +### C.5 Ability to filter data less strictly + +#### C.5.1 Motivation +Right now, the `filter` commands require all characteristics to match the customer's or property's characteristics. This is too strict, and may not be useful for the property agent. This feature will allow the property agent to filter customers or properties based on a subset of characteristics. + +#### C.5.2 Implementation +1. Allow `filter` commands to take in an extra parameter called `loose`. +2. Add code to filter customers/properties that only match one of the criteria. +3. Implement error handling and logging. + +### C.6 Budget for customer should be a range + +#### C.6.1 Motivation +Customers should be able to indicate their budget as a range instead of a set number. This is because customers may not have a fixed budget, and may be willing to pay more or less for a property. + +#### C.6.2 Implementation +1. Add code in `Budget` to have a range of values. +2. Add test cases to verify that `find`, `filter`, `match` still works with a budget range. +3. Implement error handling and logging. + +-------------------------------------------------------------------------------------------------------------------- -1. Deleting a person while all persons are being shown +## **Appendix D: Effort** - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +### D.1 Difficulty Level +Overall, the difficulty level of the tP was reasonable. PropertyMatch is evolved from AB3. Once we understood the inner workings of AB3 and how to add commands to the application, the process was relatively simple. However, there were still many things we had to pick up along the way when accomplishing the project. - 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. +### D.2 Challenges Faced +The main challenge came from having to manage two different kinds of entities -- customers and properties. Since AB3 only handled one entity, this meant that there was no model answer for handling two entities. We had to come up with our own design for how to handle two entities, and this took some time to figure out. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. +We also had to learn new technologies like Git and GitHub. Learning about good code practices as well as the forking workflow, all while working on a moderately large project was a challenge for us. It took us time to resolve some messy merge conflicts. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. +We were also faced with tight deadlines for the various milestones and submissions. This meant that we had to work efficiently and effectively to meet the deadlines. -1. _{ more test cases …​ }_ +### D.3 Effort Required +While some commands like `edit` and `list` were adapted from AB3, there were completely new commands like `find`, `match`, and `filter`. The adapted commands were also heavily modified and separated into separate commands for separate entities, like `editcust` and `editprop`. -### Saving data +We also had to create multiple new classes for the `Property` entity. This also meant that we had to come up with suitable test cases for them as well. -1. Dealing with missing/corrupted data files +Although seemingly small, we had to tinker with the regex of each command such that it accepts the proper user input that we need. This was a tedious process as we had to test each regex to ensure that it works as intended. - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +### D.4 Achievements of the project +We managed to implement all the features that we set out to do. We also managed to implement the `filter` command, which was not in the original plan. We also managed to implement the `match` command, which was a challenge for us. -1. _{ more test cases …​ }_ +We expanded our technical and non-technical proficiency, and we are proud of our wonderful journey! diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 57437026c7b..c43d9f148c7 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,195 +3,804 @@ 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. +![Logo](images/Logo.png) + +Greetings property agents! A warm welcome to our user guide, your companion for navigating and maximizing the full potential of PropertyMatch. + +-------------------------------------------------------------------------------------------------------------------- + +## Table of Contents * Table of Contents {:toc} -------------------------------------------------------------------------------------------------------------------- +## Introduction +[Back to Table of Contents](#table-of-contents) + +PropertyMatch is a **contact and property management system** that aims to help you, property agents, to organise your client profiles with your corresponding properties. This guide is designed for property agents ranging from novice tech enthusiasts to seasoned tech professionals who want to organise and streamline their client and property data. + +With PropertyMatch, you can easily +- **Match** your existing customers and properties +- **Filter** your existing customers and properties to your needs +- **Find** that customer or property you are looking for, instead of relying on your memory +- and so much more...! + +With these powerful features, you can easily capitalise on your network, allowing you to convert them into **valuable leads**. This can give you the revenue boost you needed to become an all-star property agent. + +The only tools you need to make use of the full suite of capabilities PropertyMatch has to offer are your hands and a keyboard. + +This **user guide** aims to provide you with an in-depth overview of how to set up, use, and troubleshoot PropertyMatch. Take a leap into the next section to unravel the secrets of maximizing this guide for your benefit! + +-------------------------------------------------------------------------------------------------------------------- + +## Using this Guide +[Back to Table of Contents](#table-of-contents) + +As property agents, we understand that using a [Command Line Interface (CLI)](https://en.wikipedia.org/wiki/Command-line_interface) might not be your forte. Fear not! Our application is tailored just for you with simplicity in mind, with easy to use commands that you will naturally reach to without having to remember how to use them. + +Already familiar with using a CLI? Great! Feel free to move on to the section below to begin your PropertyMatch journey! + +* Embark on your PropertyMatch journey using our [Quick Start Guide](#quick-start) - the express lane to get PropertyMatch up and running in no time! +* Afterwhich, you can head over to our [Interface Layout](#interface-layout) section to familiarise yourself with our snazzy interface and discover the ins and outs of the [CLI](#2-command-input-and-output-boxes). +* Take a look at the [Command Summary](#command-summary) - your cheatsheet for the different commands along with links to how to use them + +Let the fun begin – because who said setting up can't be a joyride? + +If you have any doubts while using PropertyMatch, do head over to our [FAQ](#faq) section to view comprehensive answers to some frequently asked questions. You may also contact us at [hello@propertymatch.com](mailto:hello@propertymatch.com) if you have any other questions. + +[//]: # (Table inspired by NUSCoursemates https://ay2324s1-cs2103t-t17-4.github.io/tp/UserGuide.html) +In addition, here are some symbols you might encounter in our guide, and their respective meanings. + +| Symbol | Meaning | +|----------------------|------------------------------------------------------------------------------------------------| +| :information_source: | Note. Provides additional information. | +| :bulb: | Helpful tip that will improve your experience. | +| :exclamation: | Warning. Attempting to perform an action with a warning will lead to undesirable consequences. | + +
+ +**:information_source: Note:**
+* These symbols will be encapsulated in a box as such. +
+ +-------------------------------------------------------------------------------------------------------------------- ## Quick start +[Back to Table of Contents](#table-of-contents) + +1. Ensure you have Java `11` or above installed in your computer. + * [Open up Terminal](#faq) on your computer. + * Once the Terminal is open, type `java -version` and hit '**Enter/ Return**'. + * The application should state your Java version as `11`. + * If you do not see `11`, this [link](https://docs.oracle.com/en/java/javase/11/install/overview-jdk-installation.html#GUID-8677A77F-231A-40F7-98B9-1FD0B48C346A) (external link to Oracle) provides a step-by-step installation guide for Java `11`. + +2. Download the latest `propertymatch.jar` from our [releases page](https://github.com/AY2324S1-CS2103T-W11-2/tp/releases). After clicking into the release page, scroll down slightly until you reach the Assets section of the page. This section should look like this:
+
![Release page](images/user-guide/ReleasePage.png) -1. Ensure you have Java `11` or above installed in your Computer. +3. Simply click on `propertymatch.jar`, and PropertyMatch should begin automatically downloading on your computer! -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +4. Copy the `propertymatch.jar` file to the folder you want to use as the _home folder_ for PropertyMatch. All data will be created and stored in that folder. If you are unsure where to place the folder, go to your desktop and create a folder. You can then copy the `propertymatch.jar` file into the folder you just created. -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +5. Open the folder and double-click on `propertymatch.jar` to open PropertyMatch. If this does not work, please [open up the terminal](#faq) on your computer and type in `java -jar propertymatch.jar` to start the application. + +6. An app similar to the one below should appear in a few seconds. Note that the app already contains some sample data.
+
![startUi](images/startUi.png) + +Congratulations! PropertyMatch is now set up and ready to work on your system. + +If you encounter any problems during the setup process, please check out the [FAQ](#faq) section of this guide, which hopefully contains some information that can help you diagnose your issue. + +
+ +**:exclamation: Caution (for advanced users):**
+* On first launch, PropertyMatch will create a few files that have the extension `.json` in its *home directory*. These files are used + by PropertyMatch to store its data.
+* **Edit these at your own risk**, as PropertyMatch will start with an empty [database](#glossary) if it detects any + error in the formatting of the data in these files. +
+ +-------------------------------------------------------------------------------------------------------------------- -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.
- ![Ui](images/Ui.png) +## Interface Layout +[Back to Table of Contents](#table-of-contents) -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: +When you launch PropertyMatch, PropertyMatch will appear on your screen as a window. Let's take a look at the 5 different components +that make up this window. - * `list` : Lists all contacts. +![UserInterface](images/user-guide/UserInterface.png) - * `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. +### 1. Menu Bar - * `delete 3` : Deletes the 3rd contact shown in the current list. +The Menu Bar allows you to exit the application and look for the link to our user guide. - * `clear` : Deletes all contacts. +### 2. Command Input and Output Boxes - * `exit` : Exits the app. +These boxes are located at the top section of the window.

+![CommandBox](images/user-guide/CommandBox.png)

+The **command input box** is located where the placeholder text `Enter command here...` is.
+Clicking on it will allow you to type commands for PropertyMatch to execute. -1. Refer to the [Features](#features) below for details of each command. +The **command output box** is located directly beneath the **command input box**. Upon execution of any command, PropertyMatch will +display some information regarding the command, regardless of whether the command is successfully or not successfully executed. +In the image above, it is displaying the message "Listed all customers", the message shown after successfully executing +the [List Customers Command](#listing-all-customers-listcust). + +
+ +**:information_source: Note:**
+* If a command is not successfully executed, the text within the command input box will turn red. +
+ +Here are some commands you can test to start with. Try it out by copy and pasting in the command input box! + +* **`listcust`** : Lists all your recorded customers. + +* **`addcust n/Tim Cook p/91234567 e/cook@apple.com b/2500000 c/bright c/sunny`** : + Adds a customer named "Tim Cook" with a specified phone number and email to the customer list. + This customer has a specified budget, and desired characteristics for the property he wants to buy. + +* **`delcust 1`** : Deletes a customer at index 1 of the [customer list](#3-customer-list) + +* **`help`** : Displays a help window. + +* **`exit`** : Exits the application. + +You can refer to the [Features](#features) below for the details of each command. + +### 3. Customer List + +You can find the customer list located at the left section of the window.

+

+The customer list displays information regarding customers who are currently stored in PropertyMatch's [database](#glossary). + +
+ +**:information_source: Note:**
+* The customer list might not be showing *all* customers at all times (check out the [FAQ](#faq) for more information). +
+ +You can also filter and modify the customer list using the commands given in the [Features](#features) section below. + +### 4. Property List + +You can find the property list located at the right section of the window.

+

+The property list displays information regarding properties that are currently stored in PropertyMatch's [database](#glossary). + +
+ +**:information_source: Note:**
+* The property list might not be showing *all* properties at all times (check out the [FAQ](#faq) for more information). +
+ +You can also filter and modify the property list using the commands given in the [Features](#features) section below. + +### 5. Help Window + +This will appear as a separate window. +![Help Window](images/user-guide/HelpWindow.png) + +The __help window__ displays a link to PropertyMatch's User Guide, which is the online version of this document. + +It appears when you execute the [Help Command](#viewing-help-help). -------------------------------------------------------------------------------------------------------------------- ## Features +[Back to Table of Contents](#table-of-contents) + +PropertyMatch's features are mostly in the form of commands you can input into the [command input box](#2-command-input-and-output-boxes). We will now go into the details about each feature of PropertyMatch. +If you just want a quick summary of all the feature PropertyMatch has, do take a look at the [command summary](#command-summary) section. -
+
**:information_source: Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +* Words in `UPPER_CASE` are the [parameters](#glossary) to be supplied by the user.
+ e.g. in `addcust n/NAME`
+ * `addcust` is the command name. In this case, this command adds a customer. + * `n/` is a prefix indicating that there is a `NAME` to be supplied. + * `NAME` is a parameter which can be used as `add n/John Doe`.
+ This command adds John Doe as a customer to the app. + * Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. + e.g `n/NAME [c/CHARACTERISTIC]` can be used as `n/Tim Cook c/smart`(with `CHARACTERISTIC`) or as `n/John Doe` (without `CHARACTERISTIC`). * 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. `[c/CHARACTERISTIC]…​` can be used as ` ` (i.e. 0 times), `c/smart`, `c/smart c/rich` etc. * Parameters can be in any order.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`. +* Extraneous parameters for commands that do not take in parameters (e.g. `help`, `exit` and `clear`) will be ignored.
+ e.g. if you entered `help 123`, it will be interpreted as `help`. * 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.
-### Viewing help : `help` +### Add Commands +[Back to Table of Contents](#table-of-contents) -Shows a message explaning how to access the help page. +Ready to start adding customer and properties into PropertyMatch? Read the following section to find out more about our add feature. -![help message](images/helpMessage.png) +#### Adding a customer: `addcust` -Format: `help` +Adds a customer and their respective contact details and information to your customer list. + +Format: `addcust n/NAME p/PHONE_NUMBER e/EMAIL b/BUDGET [c/CHARACTERISTIC]…​` + +* `n/NAME` : Name of your customer +* `p/PHONE_NUMBER` : Phone number of your customer (only 8 digits and starting with 6, 8 or 9) +* `e/EMAIL` : Email of your customer +* `b/BUDGET` : Budget of your customer (5 to 12 digits and starting with a non-zero digit) +* `c/CHARACTERISTIC` (optional) : Characteristics of the property your customer is looking for + +
+ +**:information_source: Note:**
+ +* `NAME` can be written with alphabets, numbers and spaces. +* If `NAME` starts with spaces, the starting spaces will not be registered. +* `CHARACTERISTIC` can be written with alphabets, numbers, but not spaces. +* Duplicate `CHARACTERISTIC` will be omitted, e.g. entering `c/bright c/BRIGHT` will only record 1 `bright` characteristic. +* To ensure that your customer list remains neat, PropertyMatch will not allow you to add duplicate customers with the same **phone number**. +* Only the phone number is used to differentiate between customers. i.e. Other parameters can be duplicated for different customers. + +
+ +Examples: +* `addcust n/Tim Cook p/91234567 e/cook@apple.com b/2500000 c/bright c/sunny` adds a customer named Tim Cook with all the relevant details +* `addcust n/Phoebe p/87654321 e/pb@gmail.com b/200000 c/bright c/sunny c/white` adds a customer named Phoebe with all the relevant details + +
+ +**:bulb: Tip:**
+ +* If you encounter an error, ensure that you have typed the command accurately with **all** the compulsory [parameters](#glossary) present. (refer to the example commands above) +* The characteristics are optional. If it is not set, no characteristic will be recorded for that particular customer. +
+ +You should see the message in the [output box](#2-command-input-and-output-boxes) as below when a customer is successfully added. The new customer will be added to the bottom of your list for easy reference!
+
![addcustUi](images/user-guide/addcustUi.png) + + + +#### Adding a property: `addprop` + +Adds a property and the respective contact details of the homeowner and information about the property to your property list. + +Format: `addprop n/NAME a/ADDRESS p/PHONE_NUMBER pr/PRICE [c/CHARACTERISTIC]…​` + +* `n/NAME` : Name of the property +* `a/ADDRESS` : Address of the property +* `p/PHONE_NUMBER` : Phone number of the owner of the property (8 digits and starting with 6, 8 or 9) +* `pr/PRICE` : Price of the property (5 to 12 digits and starting with a non-zero digit) +* `c/CHARACTERISTIC` (Optional) : Characteristics of the property + +
+ +**:information_source: Note:**
+ +* `NAME` and `ADDRESS` can be any character, except a slash +* If `NAME` or `ADDRESS`starts with spaces, the starting spaces will not be registered. +* Additional spaces in `ADDRESS` will be considered as unique properties, e.g. `J'den` and `J' den` will be considered as 2 distinct properties. +* `CHARACTERISTIC` can be written with alphabets, numbers, but not spaces. +* Duplicate `CHARACTERISTIC` will be omitted, e.g. entering `c/bright c/BRIGHT` will only record 1 `bright` characteristic. +* To ensure that your property list remains neat, PropertyMatch will not allow you to add duplicate properties with the same **address**. +* Only the address is used to differentiate between properties. i.e. Other parameters can be duplicated for different properties. +
+ +Examples: +* `addprop n/Aqua Heights a/195 Paya Lebar 3 #18-32 p/91135235 pr/700000` adds a property with all the relevant details and no characteristics +* `addprop n/Skyview a/214 Clementi Ave 2 #09-78 p/98835235 pr/500000 c/bright c/sunny c/big c/square` adds a property with all the relevant details and 3 characteristics +
+ +**:bulb: Tip:**
+* Similar to adding a customer, ensure that you have typed the command accurately with **all** the compulsory [parameters](#glossary) present. (refer to the example commands above)
+* The characteristics are also optional. If it is not set, no characteristic will be recorded for that particular property. +
+ +
+ +**:information_source: Note:**
+ + +
+ +You should get a result similar to [adding customers](#adding-a-customer-addcust) when the property is successfully added! + +### List Commands +[Back to Table of Contents](#table-of-contents) + +Want to see all your customers and property details all at one go? Read the following section to find out more about our list feature. + +
+ +**:bulb: Tip:**
+ +* The List Commands should be used to view all buyers and properties again, after a [Filter Command](#filter-commands), [Find Command](#find-commands), or [Match Command](#match-commands) is executed. +
+ +#### Listing all customers: `listcust` + +Updates the [Customer List](#3-customer-list) to show all added customers for ease of viewing. + +Format: `listcust` + +No additional [parameters](#glossary) are needed for this command, and they will be ignored if used. + + +#### Listing all properties: `listprop` + +Updates the [Property List](#4-property-list) to show all added properties for ease of viewing. + +Format: `listprop` + +No additional [parameters](#glossary) are needed for this command, and they will be ignored if used. + + +### Delete Commands +[Back to Table of Contents](#table-of-contents) + +Already made a sale and want to remove a customer's or property's details? Read the following section to find out more about our delete feature. + +#### Deleting a customer: `delcust` + +Deletes the specified customer and their corresponding details from your customer list. + +Format: `delcust INDEX` + +* Deletes your customer at the specified `INDEX`. +* The index refers to the index number beside your customers' names shown in the **displayed** customer list. +* Acceptable indexes are **positive integers** (e.g. 1, 2, 3…​) within the customer list size. + +
+ +**:bulb: Tip:**
+* `INDEX` can start with 0, e.g. `delcust 02` or `delcust 0002` deletes the 2nd customer in the customer list. +
+ +
+ +**:information_source: Note:**
+ +* `INDEX` should not be more than 2 billion as our app does not support more than 2 billion customers! +
+ +Examples: +* `listcust` followed by `delcust 2` deletes the 2nd customer in the displayed customer list. + +Upon successfully deleting a customer, the confirmation message will appear in the [output box](#2-command-input-and-output-boxes) as shown below! + +![delcustcustUi](images/user-guide/delcustUi.png) + +#### Deleting a property: `delprop` + +Deletes the specified property and its corresponding details from your property list. + +Format: `delprop INDEX` + +* Deletes the property at the specified `INDEX`. +* The index refers to the index number beside your properties' names shown in the **displayed** property list. +* Acceptable indexes are **positive integers** (e.g. 1, 2, 3…​) within the property list size. +
-### Adding a person: `add` +**:bulb: Tip:**
+* `INDEX` can start with 0, e.g. `delprop 02` or `delprop 0002` deletes the 2nd property in the property list. +
+ +
+ +**:information_source: Note:**
+ +* `INDEX` should not be more than 2 billion as our app does not support more than 2 billion properties! +
+ +Examples: +* `listprop` followed by `delprop 2` deletes the 2nd property in the displayed property list. + +The result will be similar to [deleting customers](#deleting-a-customer-delcust) when the property is successfully deleted! + +
+ +**:exclamation: Caution:**
+ +* Remember to use the `listcust` or `listprop` command before using any commands that require a `INDEX`! If not, the `INDEX` will be with respect to whatever is on the screen at the time of command input. +* This applies to [Edit Commands](#edit-commands) and [Match Commands](#match-commands) as well. + +
+ +### Edit Commands +[Back to Table of Contents](#table-of-contents) + +Customer or property details might change after some time and this is where our edit commands can come in handy! Read the following section to find out more about our edit feature. + +#### Editing a customer: `editcust` + +Edits the details of your customer. + +Format: `editcust INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [b/BUDGET] [c/CHARACTERISTIC]…​` +* Edits your customer at the specified `INDEX`. Existing details will be updated to the details you entered. +* The index refers to the index number beside your customers' names shown in the **displayed** customer list. +* Acceptable indexes are **positive integers** (e.g. 1, 2, 3…​) within the customer list size. +* **At least one** of the optional [parameters](#glossary) must be provided. + +Examples: +* `editcust 2 p/91111111 e/andrew@gmail.com` Edits the phone number and email of the 2nd customer to be `91234567` and `andrew@gmail.com` respectively. +* `editcust 1 n/Andrew c/` Edits the name of the 1st customer to be `Andrew` and clears all existing characteristics. + +
+ +**:information_source: Note:**
+ +* To ensure that your customer list remains neat, PropertyMatch will not allow you to edit your customer to have the same **phone number** as an existing customer. +* Only the phone number is used to differentiate between customers. i.e. Other [parameters](#glossary) can be edited to be the same as other customers. +* Duplicate `CHARACTERISTIC` will be omitted, e.g. entering`c/bright c/BRIGHT` will only record 1 `bright` characteristic. + +
+ +
+ +**:bulb: Tip:**
+* `INDEX` can start with 0, e.g. `editcust 02` or `editcust 0002` edits the 2nd customer in the customer list. +* When editing characteristics, existing `CHARACTERISTIC` of your customer will be removed i.e. adding of characteristics is not cumulative. +* You can remove all your customer’s `CHARACTERISTIC` by typing `c/` without specifying any `CHARACTERISTIC` after it. + +
+ +You should see a similar message in the [output box](#2-command-input-and-output-boxes) as below when your customer's details is successfully edited. + +When `editcust 1 n/Andrew c/` is entered + +![editcustUi](images/user-guide/editcustUi.png) + + +#### Editing a property: `editprop` +Edits the details of your property. + +Format: `editprop INDEX [n/NAME] [p/PHONE_NUMBER] [pr/PRICE] [a/ADDRESS] [c/CHARACTERISTIC]…​` +* Edits the property at the specified `INDEX`. Existing details will be updated to the details you entered. +* The index refers to the index number beside your properties' names shown in the **displayed** property list. +* Acceptable indexes are **positive integers** (e.g. 1, 2, 3…​) within the property list size. +* **At least one** of the optional [parameters](#glossary) must be provided. + +Examples: +* `editprop 1 p/91234567 a/43 Clementi Avenue 3 #03-543` Edits the phone number and address of the 1st property to be `91234567` and `43 Clementi Avenue 3 #03-543` respectively. +* `editprop 2 n/Skyview c/` Edits the name of the 2nd property to be `Skyview` and clears all existing characteristics. + +
+ +**:information_source: Note:**
+* To ensure that your property list remains neat, PropertyMatch will not allow you to edit your property to have the same **address** as an existing property. +* Only the address is used to differentiate between properties. i.e. Other [parameters](#glossary) can be edited to be the same as other properties. +* Additional spaces in `ADDRESS` will be considered as unique properties, e.g. `J'den` and `J' den` will be considered as 2 distinct properties. +* Duplicate `CHARACTERISTIC` will be omitted, e.g. entering`c/bright c/BRIGHT` wil only record 1 `bright`characteristic. + +
+ +
+ +**:bulb: Tip:**
+* `INDEX` can start with 0, i.e. `editprop 02` or `editprop 0002` edits the 2nd property in the property list. +* When editing characteristics, existing `CHARACTERISTIC` of your property will be removed i.e adding of characteristics is not cumulative. +* You can remove all your property’s `CHARACTERISTIC` by typing `c/` without specifying any `CHARACTERISTIC` after it. + +
+ +Successfully editing your property would produce a similar result as [editing a customer](#editing-a-customer-editcust)! + +### Find Commands +[Back to Table of Contents](#table-of-contents) + +Can't seem to find the customer or property you are looking for? Read the following section to find out more about our find feature. + +#### Finding a customer: `findcust` + +Finds and returns a customer or a list of customers, from all your customers whose name **begins** with `NAME` at **any position** within their name. + +Format: `findcust NAME` + +* The `NAME` must be in the same language as the name, i.e English. +* The `NAME` should only contain the relevant alphabets or numbers but no symbols. + +Examples: +* `listcust` followed by `findcust F` finds and returns the customers whose names contains any word that starts with `F` in the customer list, such as `Fredy Lawrence` or `Sara Foo`. +* `listcust` followed by `findcust B D` finds and returns the customers whose names contains any word that starts with `B` or `D` in the customer list, such as `Doraemon`, `Boraemon`,`Sara Doo`, `Sara Boo`, or `Bara Doo`. + +You should see the message in the [output box](#2-command-input-and-output-boxes) as shown below when you have successfully found your customers. + +When `findcust B D` is entered. + +![findcustUi](images/user-guide/findcustUi.png) + + +#### Finding a property: `findprop` + +Finds and returns a property or a list of properties, from all your properties whose name **begins** with `NAME` at **any position** within its name. + +Format: `findprop NAME` + +* The `NAME` must be in the same language as the name, i.e English. +* The `NAME` should only contain the relevant alphabets, numbers or symbols (excluding slashes). + +Examples: +* `listprop` followed by `findprop F` finds and returns the properties with names that begin with "F" in the property list, such as `Fernvale Square` or `Dairy Farm`. +* `listprop` followed by `findprop F J` finds and returns the properties with names that begin with "F" or "J" in the property list, such as `Fernvale Square`, `Dairy Farm`, `J'den` or `Changi Jail`. + +Successfully finding the property you want would produce a similar result as [finding a customer](#finding-a-customer-findcust)! + + +### Filter Commands +[Back to Table of Contents](#table-of-contents) -Adds a person to the address book. +Want to only see selected customers or properties? Read the following section to find out more about our filter feature. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +#### Filter customers: `filtercust` + +Filters all your customers to only show customers that fit your criteria. + +Format: `filtercust [b/BUDGET] [c/CHARACTERISTIC]…​` + +* Filter and return the customers whose budget is greater than or equals to `BUDGET` and have **all** the `CHARACTERISTIC`. +* `b/BUDGET` (optional) : Budget of your customer +* `c/CHARACTERISTIC` (optional) : Characteristics of the property your customer is looking for + +
+ +**:information_source: Note:**
+* While both `BUDGET` and `CHARACTERISTIC` are optional, at least one of them should be present. + +
+ +
+ +**:bulb: Tip:**
+* Omitting`BUDGET` will return customers in search of properties with the specified `CHARACTERISTIC`.
+* Omitting `CHARACTERISTIC` will return customers with a budget greater than or equal to `BUDGET`. -
:bulb: **Tip:** -A person can have any number of tags (including 0)
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` +* `filtercust b/250000 c/white c/big` lists all customers with a budget greater than or equals to 250000 and has the characteristics white and big. +* `filtercust c/white` lists all customers with white as their characteristic. + +You should see the message in the [output box](#2-command-input-and-output-boxes) as below when you have successfully filtered your customers. + +When `filtercust c/white` is entered. + +![filtercustUi](images/user-guide/filtercustUi.png) + + +#### Filter properties: `filterprop` + +Filters all your properties to only show properties that fit your criteria. + +Format: `filterprop [pr/PRICE] [c/CHARACTERISTIC]…​` + +* Filter and return properties priced lower than or equals to `PRICE` and have **all** the `CHARACTERISTIC`. +* `pr/PRICE` (optional) : The price of the property +* `c/CHARACTERISTIC` (optional) : The characteristics of the property -### Listing all persons : `list` +
-Shows a list of all persons in the address book. +**:information_source: Note:**
+* While both `PRICE` and `CHARACTERISTIC` are optional, at least one of them should be present. -Format: `list` +
-### Editing a person : `edit` +
-Edits an existing person in the address book. +**:bulb: Tip:**
+* Omitting `PRICE` will return the properties which have all the `CHARACTERISTIC`.
+* Omitting `CHARACTERISTIC` will return properties priced lower than or equal to `PRICE`. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +* `filterprop pr/250000 c/white c/big` lists all the properties priced lower than or equals to 2500000 with white and big as 2 of its characteristics. +* `filterprop c/white` lists all properties with white as one of its characteristic + +Successfully filtering your properties would produce a similar result as [filtering your customers](#filter-customers-filtercust)! + +### Match Commands +[Back to Table of Contents](#table-of-contents) + +Ready to match customers and properties? Our unique matching feature has the power to match customers and properties easily! Read the following section to find out more about our match feature. + +#### Matching properties to a customer: `matchcust` + +Shows the list of properties that matches the criteria of your customer. + +Format: `matchcust INDEX` + +* Matches the customer at the specified `INDEX`. +* The index refers to the index number beside your customers' names shown in the **displayed** customer list. +* Acceptable indexes are **positive integers** (e.g. 1, 2, 3…​) within the customer list size. -### Locating persons by name: `find` +For `matchcust`, a property will be matched to your specified customer, if and only if:
+* The price of the property is less than or equal to budget of the customer, +* At least 1 matching characteristic (If the customer has any characteristic recorded), +* If the customer has no characteristic recorded, only the budget requirement needs to be met. -Finds persons whose names contain any of the given keywords. +
-Format: `find KEYWORD [MORE_KEYWORDS]` +**:bulb: Tip:**
-* 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` +* `INDEX` can start with 0, i.e. `matchcust 02` or `matchcust 0002` matches the 2nd customer in the customer list to potential properties. + +
Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +* `matchcust 1` +* `matchcust 10` + +A message in the [output box](#2-command-input-and-output-boxes) similar to the one below should appear when you have successfully matched your customers! + +![Ui](images/user-guide/Ui.png) + +#### Matching customers to a property: `matchprop` + +Shows the list of customers that matches the criteria of your property. + +Format: `matchprop INDEX` + +* Matches the property at the specified `INDEX`. +* The index refers to the index number beside your properties' names shown in the **displayed** property list. +* Acceptable indexes are **positive integers** (e.g. 1, 2, 3…​) within the property list size. -### Deleting a person : `delete` +For `matchprop`, a customer will be matched to your specified property, if and only if:
+* The budget of customer is greater than or equal to the price of the property, +* At least 1 matching characteristic (If the property has any characteristics), +* If the property has no characteristic, only the price requirement needs to be met. -Deletes the specified person from the address book. +
-Format: `delete INDEX` +**:bulb: Tip:**
-* 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, …​ +* `INDEX` can start with 0, i.e. `matchprop 02` or `matchprop 0002` matches the 2nd property in the property list to potential customers. + +
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. +* `matchprop 1` +* `matchprop 10` + +Matching your properties successfully will output a similar message as [matching your customers](#matching-properties-to-a-customer-matchcust). + +### General Features + +[Back to Table of Contents](#table-of-contents) -### Clearing all entries : `clear` +Want to start fresh and clear all your existing data? Exit the application? Or in need of assistance? Read the follow section to find out more about our clear, exit and help feature. -Clears all entries from the address book. +#### Clear the data in the application: `clear` + +Resets all data in the application. (i.e. Deletes all entries in your [Customer List](#3-customer-list) and [Property List](#4-property-list)) Format: `clear` -### Exiting the program : `exit` +
+ +**:exclamation: Caution:**
+* Clearing the data in your application will result in all data being lost! Be careful when you perform this operation and be sure that you want to reset all data in the app. + +
-Exits the program. +#### Exiting the program: `exit` + +Displays a goodbye message. Exit the application after 3 seconds. Format: `exit` -### Saving the data -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +#### Viewing help: `help` + +Displays a window containing the link to PropertyMatch's user guide if you need further help. -### Editing the data file +Format: `help` -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. -
: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. -
+#### Saving data + +PropertyMatch data is saved in the [hard disk](#glossary) automatically after any command that changes the data. There is no need to save manually. + + +#### Editing the data file + +PropertyMatch's data is saved as 2 separate JSON files `[JAR file location]/data/addressbook.json` and `[JAR file location]/data/propertybook.json`. Advanced users are welcome to update data directly by editing those JSON files. -### Archiving data files `[coming in v2.0]` -_Details coming soon ..._ +
+ +**:exclamation: Caution (for advanced users):**
+* If your changes to the data file makes its format invalid, PropertyMatch will discard all data and start with an empty data file at the next run. Hence, it is recommended to make a backup of the file before editing it. +
+
+ -------------------------------------------------------------------------------------------------------------------- ## FAQ +[Back to Table of Contents](#table-of-contents) **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. +**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 PropertyMatch home folder. + +**Q**: How do I install Java 11?
+**A**: Follow this [link](https://docs.oracle.com/en/java/javase/11/install/overview-jdk-installation.html#GUID-8677A77F-231A-40F7-98B9-1FD0B48C346A) for steps to download Java 11. + +**Q**: Help! I can’t seem to get a command to work…
+**A**: Refer to the [features](#features) section of our guide for command information and syntax. Make sure that you have supplied all necessary inputs for the command and specified the flags in a correct manner. + +**Q**: Why is PropertyMatch not displaying all customers/properties in the [database](#glossary)?
+**A**: It's possible that the application may not show all customers/properties all the time due to specific commands like [filtering](#filter-commands), [finding](#find-commands), or [matching](#match-commands) that have been executed. If you're encountering this issue, consider using the [list commands](#list-commands) to ensure you're viewing the complete list. + +**Q**: I don’t understand some terms used in the guide…
+**A**: Please check out the [glossary](#glossary) and see if the term that you are confused about is documented there! + +**Q**: I deleted my data file! Is there any way to recover the data that I lost?
+**A**: Try looking in your computer’s trash bin on macOS or recycle bin on Windows for the files that were deleted. If the files can’t be found, then we apologise, but there is currently no way for you to retrieve lost data. 🙁 + +**Q**: How do I uninstall PropertyMatch?
+**A**: We are sad to see you go 🙁 PropertyMatch is not installed onto your [hard drive](#glossary), so you only need to delete the folder that contains propertymatch.jar (that is, the home folder of PropertyMatch). + +**Q**: Do I need an active internet connection to use PropertyMatch?
+**A**: No, PropertyMatch is a standalone application that does not require an internet connection to function. However, you'll need an internet connection to download it to your machine. + +**Q**: I can't find what i need...
+**A**: The guide is divided into sections for easy navigation. Use the [table of contents](#table-of-contents) to locate what you are looking for. + +**Q**: How do I open Terminal?
+**A**: The method for opening up the Terminal will be different for every operating system: +* If you are using Windows, press the '**Windows**' key and search for '**Terminal**'.
+* If you are using a Mac, click on '**F4**' and search for '**Terminal**'.
+* If you are using a Linux system, press '**Ctrl**' + '**Alt**' + '**T**' keys simultaneously. + -------------------------------------------------------------------------------------------------------------------- ## Known issues +[Back to Table of Contents](#table-of-contents) -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. +1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the app will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. -------------------------------------------------------------------------------------------------------------------- ## Command summary +[Back to Table of Contents](#table-of-contents) + +| Action | Format, Examples | +|----------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **[Add customer](#adding-a-customer-addcust)** | `addcust n/NAME p/PHONE_NUMBER e/EMAIL b/BUDGET [c/CHARACTERISTIC]…​`
e.g., `addcust n/Phoebe p/87654321 e/pb@gmail.com b/200000 c/bright c/sunny c/white` | +| **[Add property](#adding-a-property-addprop)** | `addprop n/NAME a/ADDRESS p/PHONE_NUMBER pr/PRICE [c/CHARACTERISTIC]…​`
e.g., `addprop n/Aqua Heights a/195 Paya Lebar 3 #18-32 p/91135235 pr/700000` | +| **[List properties](#listing-all-customers-listcust)** | `listcust` | +| **[List customers](#listing-all-properties-listprop)** | `listprop` | +| **[Delete customer](#deleting-a-customer-delcust)** | `delcust INDEX`
e.g., `delcust 3` | +| **[Delete property](#deleting-a-property-delprop)** | `delprop INDEX`
e.g., `delprop 3` | +| **[Edit customer](#editing-a-customer-editcust)** | `editcust INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [b/BUDGET] [c/CHARACTERISTIC]…​`
e.g., `editcust 1 p/91234567 e/andrew@gmail.com` | +| **[Edit property](#editing-a-property-editprop)** | `editprop INDEX [n/NAME] [a/ADDRESS] [p/PHONE_NUMBER] [pr/PRICE] [c/CHARACTERISTIC]…​`
e.g., `editprop 1 ph/91234567 a/43 Clementi Avenue 3 #03-543` | +| **[Find customers](#finding-a-customer-findcust)** | `findcust NAME`
e.g., `findcust Amy` | +| **[Find properties](#finding-a-property-findprop)** | `findprop NAME`
e.g., `findprop Skyview` | +| **[Filter customers](#filter-customers-filtercust)** | `filtercust [b/BUDGET] [c/CHARACTERISTIC]…​`
e.g., `filtercust b/250000 c/white` | +| **[Filter properties](#filter-properties-filterprop)** | `filterprop [pr/PRICE] [c/CHARACTERISTIC]…​`
e.g., `filterprop pr/250000 c/white` | +| **[Match properties to customer](#matching-properties-to-a-customer-matchcust)** | `matchcust INDEX`
e.g., `matchcust 1` | +| **[Match customers to property](#matching-customers-to-a-property-matchprop)** | `matchprop INDEX`
e.g., `matchprop 1` | +| **[Clear](#clear-the-data-in-the-application-clear)** | `clear` | +| **[Exit](#exiting-the-program-exit)** | `exit` | +| **[Help](#viewing-help-help)** | `help` | + +-------------------------------------------------------------------------------------------------------------------- + +## Glossary +[Back to Table of Contents](#table-of-contents) -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` +| Term | Description | +|------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Command Line Interface (CLI) | A text-based way to interact with a computer or software by typing commands into a command prompt. Instead of using buttons and icons, you enter commands to perform tasks. | +| Parameter | Think of parameters like customizing your command. If you're telling someone to bake a cake, the flavor, size, and frosting type are your parameters. In PropertyMatch commands, parameters are the details you provide to make the command do exactly what you need. They're like the specific settings for your command. | +| Database | A database is like a digital filing cabinet where we store and organize information for easy access and management. It's a structured collection of data, simplifying how we keep things in order. | +| Hard Disk | An internal or external computer component that stores data, such as the operating system, applications, and user files. | diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..4e0a44ceb23 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "PropertyMatch" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2324S1-CS2103T-W11-2/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..b30dde0c6e7 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: "PropertyMatch"; font-size: 32px; } } diff --git a/docs/diagrams/AddCustomerActivityDiagram.puml b/docs/diagrams/AddCustomerActivityDiagram.puml new file mode 100644 index 00000000000..6d76d54e6c8 --- /dev/null +++ b/docs/diagrams/AddCustomerActivityDiagram.puml @@ -0,0 +1,23 @@ +@startuml +start +:User executes add customer command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([User provided all required parameters]) +:Customer's name, budget, phone number, email are parsed; +if () then ([characteristics provided]) +:Customer's characteristics is parsed; +note left + For a customer, only its + characteristics are optional. +end note +else([else]) +endif +#palegreen:New customer is returned; +stop +else([User failed to provide required parameters correctly]) +#pink:PropertyMatch displays an error; +end +@enduml diff --git a/docs/diagrams/AddPropertyActivityDiagram.puml b/docs/diagrams/AddPropertyActivityDiagram.puml new file mode 100644 index 00000000000..88706c7282d --- /dev/null +++ b/docs/diagrams/AddPropertyActivityDiagram.puml @@ -0,0 +1,23 @@ +@startuml +start +:User executes add property command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([User provided all required parameters]) +:Property's name, price, phone number, are parsed; +if () then ([characteristics provided]) +:Property's characteristics is parsed; +note left + For a property, only its + characteristics are optional. +end note +else([else]) +endif +#palegreen:New property is returned; +stop +else([User failed to provide required parameters correctly]) +#pink:PropertyMatch displays an error; +end +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index 48b6cc4333c..fbcc1077a67 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -8,13 +8,13 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "delcust 1" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delcust 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteCustomer(p) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 598474a5c82..a0fab42ef10 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -10,12 +10,12 @@ UniqueTagList -[hidden]down- UniquePersonList UniqueTagList -[hidden]down- UniquePersonList UniqueTagList -right-> "*" Tag -UniquePersonList -right-> Person +UniquePersonList -right-> Customer -Person -up-> "*" Tag +Customer -up-> "*" Tag -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address +Customer *--> Name +Customer *--> Phone +Customer *--> Email +Customer *--> Budget @enduml diff --git a/docs/diagrams/CustomerObjectDiagram.puml b/docs/diagrams/CustomerObjectDiagram.puml new file mode 100644 index 00000000000..d15c33557db --- /dev/null +++ b/docs/diagrams/CustomerObjectDiagram.puml @@ -0,0 +1,27 @@ +@startuml +skinparam ActivityFontSize 15 +skinparam ArrowFontSize 12 + +package CustomerObjectDiagram { + Object ":Customer" as Customer { + } + + Object ":Name" as Name { + } + Object ":Phone" as Phone { + } + Object ":Email" as Email { + } + Object ":Budget" as Budget { + } + Object ":Tag" as Tag { + } +} + +Name <-- Customer : has < +Phone <-- Customer : has < +Email <-- Customer : has < +Budget <-- Customer : has < +Tag <-- Customer : has < + +@enduml diff --git a/docs/diagrams/DeleteCustomerActivityDiagram.puml b/docs/diagrams/DeleteCustomerActivityDiagram.puml new file mode 100644 index 00000000000..392cab7e1e4 --- /dev/null +++ b/docs/diagrams/DeleteCustomerActivityDiagram.puml @@ -0,0 +1,22 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:User inputs a `delcust INDEX` command; +:Logic Manager passes command to AddressBookParser; +:DeleteCommandParser parses the command; +if () then ([Command is valid]) +:Parser extracts `INDEX` parameter from command; +:Logic Manager executes DeleteCustomer(INDEX); +:Customer data is deleted from the AddressBook; +:CommandResult is returned; + +else ([Command is invalid]) +:Error for invalid command is thrown; + +endif +:Results are displayed to user; + +stop + +@enduml diff --git a/docs/diagrams/DeleteCustomerSequenceDiagram.puml b/docs/diagrams/DeleteCustomerSequenceDiagram.puml new file mode 100644 index 00000000000..39e9ed1a69e --- /dev/null +++ b/docs/diagrams/DeleteCustomerSequenceDiagram.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 ":DeleteCustomerCommandParser" as DeleteCustomerCommandParser LOGIC_COLOR +participant "d:DeleteCustomerCommand" as DeleteCustomerCommand 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("delcust 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("delcust 1") +activate AddressBookParser + +create DeleteCustomerCommandParser +AddressBookParser -> DeleteCustomerCommandParser +activate DeleteCustomerCommandParser + +DeleteCustomerCommandParser --> AddressBookParser +deactivate DeleteCustomerCommandParser + +AddressBookParser -> DeleteCustomerCommandParser : parse("1") +activate DeleteCustomerCommandParser + +create DeleteCustomerCommand +DeleteCustomerCommandParser -> DeleteCustomerCommand +activate DeleteCustomerCommand + +DeleteCustomerCommand --> DeleteCustomerCommandParser : d +deactivate DeleteCustomerCommand + +DeleteCustomerCommandParser --> AddressBookParser : d +deactivate DeleteCustomerCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteCustomerCommandParser -[hidden]-> AddressBookParser +destroy DeleteCustomerCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> DeleteCustomerCommand : execute() +activate DeleteCustomerCommand + +DeleteCustomerCommand -> Model : deleteCustomer(1) +activate Model + +Model --> DeleteCustomerCommand +deactivate Model + +create CommandResult +DeleteCustomerCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteCustomerCommand +deactivate CommandResult + +DeleteCustomerCommand --> LogicManager : result +deactivate DeleteCustomerCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeletePropertySequenceDiagram.puml b/docs/diagrams/DeletePropertySequenceDiagram.puml new file mode 100644 index 00000000000..40ad1d55267 --- /dev/null +++ b/docs/diagrams/DeletePropertySequenceDiagram.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 ":DeletePropertyCommandParser" as DeletePropertyCommandParser LOGIC_COLOR +participant "d:DeletePropertyCommand" as DeletePropertyCommand 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("delprop 1") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("delprop 1") +activate AddressBookParser + +create DeletePropertyCommandParser +AddressBookParser -> DeletePropertyCommandParser +activate DeletePropertyCommandParser + +DeletePropertyCommandParser --> AddressBookParser +deactivate DeletePropertyCommandParser + +AddressBookParser -> DeletePropertyCommandParser : parse("1") +activate DeletePropertyCommandParser + +create DeletePropertyCommand +DeletePropertyCommandParser -> DeletePropertyCommand +activate DeletePropertyCommand + +DeletePropertyCommand --> DeletePropertyCommandParser : d +deactivate DeletePropertyCommand + +DeletePropertyCommandParser --> AddressBookParser : d +deactivate DeletePropertyCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeletePropertyCommandParser -[hidden]-> AddressBookParser +destroy DeletePropertyCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> DeletePropertyCommand : execute() +activate DeletePropertyCommand + +DeletePropertyCommand -> Model : deleteProperty(1) +activate Model + +Model --> DeletePropertyCommand +deactivate Model + +create CommandResult +DeletePropertyCommand -> CommandResult +activate CommandResult + +CommandResult --> DeletePropertyCommand +deactivate CommandResult + +DeletePropertyCommand --> LogicManager : result +deactivate DeletePropertyCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 40ea6c9dc4c..bf15acf38ab 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -49,7 +49,7 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute() activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : delete(1) activate Model Model --> DeleteCommand diff --git a/docs/diagrams/EditCustomerSequenceDiagram.puml b/docs/diagrams/EditCustomerSequenceDiagram.puml new file mode 100644 index 00000000000..9bd8443587b --- /dev/null +++ b/docs/diagrams/EditCustomerSequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":EditCustomerCommandParser" as EditCustomerCommandParser LOGIC_COLOR +participant "command:EditCustomerCommand" as EditCustomerCommand 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("editcust 1 n/Janet") +activate LogicManager + +LogicManager -> AddressBookParser : parse("edistcust 1 n/Janet") +activate AddressBookParser + +create EditCustomerCommandParser +AddressBookParser -> EditCustomerCommandParser : +activate EditCustomerCommandParser + +EditCustomerCommandParser --> AddressBookParser +deactivate EditCustomerCommandParser + +AddressBookParser -> EditCustomerCommandParser : parse("1 n/Janet") +activate EditCustomerCommandParser + +create EditCustomerCommand +EditCustomerCommandParser -> EditCustomerCommand : +activate EditCustomerCommand + +EditCustomerCommand --> EditCustomerCommandParser : command +deactivate EditCustomerCommand + +EditCustomerCommandParser --> AddressBookParser : command +deactivate EditCustomerCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditCustomerCommandParser -[hidden]-> AddressBookParser +destroy EditCustomerCommandParser + +AddressBookParser --> LogicManager : command +deactivate AddressBookParser + +LogicManager -> EditCustomerCommand : execute() +activate EditCustomerCommand + +EditCustomerCommand -> EditCustomerCommand : create(customerToEdit, descriptor) +activate EditCustomerCommand +EditCustomerCommand --> EditCustomerCommand : editedCustomer +deactivate EditCustomerCommand + +EditCustomerCommand -> Model : setCustomer(customerToEdit, editedCustomer) + +EditCustomerCommand -> Model : updateFilteredCustomerList(ALL_CUST) + +create CommandResult +EditCustomerCommand -> CommandResult : +activate CommandResult + +CommandResult --> EditCustomerCommand +deactivate CommandResult + +EditCustomerCommand --> LogicManager : commandResult +deactivate EditCustomerCommand + +[<--LogicManager : commandResult +deactivate LogicManager +@enduml diff --git a/docs/diagrams/EditPropertySequenceDiagram.puml b/docs/diagrams/EditPropertySequenceDiagram.puml new file mode 100644 index 00000000000..3832531eaa4 --- /dev/null +++ b/docs/diagrams/EditPropertySequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":EditPropertyCommandParser" as EditPropertyCommandParser LOGIC_COLOR +participant "command:EditPropertyCommand" as EditPropertyCommand 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("editprop 1 n/Skyview") +activate LogicManager + +LogicManager -> AddressBookParser : parse("editprop 1 n/Skyview") +activate AddressBookParser + +create EditPropertyCommandParser +AddressBookParser -> EditPropertyCommandParser : +activate EditPropertyCommandParser + +EditPropertyCommandParser --> AddressBookParser +deactivate EditPropertyCommandParser + +AddressBookParser -> EditPropertyCommandParser : parse("1 n/Skyview") +activate EditPropertyCommandParser + +create EditPropertyCommand +EditPropertyCommandParser -> EditPropertyCommand : +activate EditPropertyCommand + +EditPropertyCommand --> EditPropertyCommandParser : command +deactivate EditPropertyCommand + +EditPropertyCommandParser --> AddressBookParser : command +deactivate EditPropertyCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +EditPropertyCommandParser -[hidden]-> AddressBookParser +destroy EditPropertyCommandParser + +AddressBookParser --> LogicManager : command +deactivate AddressBookParser + +LogicManager -> EditPropertyCommand : execute() +activate EditPropertyCommand + +EditPropertyCommand -> EditPropertyCommand : create(propertyToEdit, descriptor) +activate EditPropertyCommand +EditPropertyCommand --> EditPropertyCommand : editedProperty +deactivate EditPropertyCommand + +EditPropertyCommand -> Model : setProp(prop, editedProp) + +EditPropertyCommand -> Model : updateFilteredPropertyList(ALL_PROPS) + +create CommandResult +EditPropertyCommand -> CommandResult : +activate CommandResult + +CommandResult --> EditPropertyCommand +deactivate CommandResult + +EditPropertyCommand --> LogicManager : commandResult +deactivate EditPropertyCommand + +[<--LogicManager : commandResult +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FilterCustomerSequenceDiagram.puml b/docs/diagrams/FilterCustomerSequenceDiagram.puml new file mode 100644 index 00000000000..0165c1d5c1c --- /dev/null +++ b/docs/diagrams/FilterCustomerSequenceDiagram.puml @@ -0,0 +1,111 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FilterCustomerCommandParser" as FilterCustomerCommandParser LOGIC_COLOR +participant ":Budget" as Budget LOGIC_COLOR +participant ":Tag" as Tag LOGIC_COLOR +participant "descriptor:FilterCustomerDescriptor" as FilterCustomerDescriptor LOGIC_COLOR +participant ":BudgetAndTagsInRangePredicate" as BudgetAndTagsInRangePredicate LOGIC_COLOR +participant "command:FilterCustomerCommand" as FilterCustomerCommand 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("filtercust b/100000 c/pink") +activate LogicManager + +LogicManager -> AddressBookParser : parse("filtercust b/100000 c/pink") +activate AddressBookParser + +create FilterCustomerCommandParser +AddressBookParser -> FilterCustomerCommandParser : +activate FilterCustomerCommandParser + +FilterCustomerCommandParser --> AddressBookParser +deactivate FilterCustomerCommandParser + +AddressBookParser -> FilterCustomerCommandParser : parse("b/100000 c/pink") +activate FilterCustomerCommandParser + +create FilterCustomerDescriptor +FilterCustomerCommandParser -> FilterCustomerDescriptor: +activate FilterCustomerDescriptor + +FilterCustomerDescriptor --> FilterCustomerCommandParser: descriptor +deactivate FilterCustomerDescriptor + +create Budget +FilterCustomerCommandParser -> Budget: +activate Budget + +Budget --> FilterCustomerCommandParser: budget +deactivate Budget + +FilterCustomerCommandParser -> FilterCustomerDescriptor: setBudget(budget) +activate FilterCustomerDescriptor +deactivate FilterCustomerDescriptor + +create Tag +FilterCustomerCommandParser -> Tag: +activate Tag + +Tag --> FilterCustomerCommandParser: tag +deactivate Tag + +FilterCustomerCommandParser -> FilterCustomerDescriptor: setTags(tags) +activate FilterCustomerDescriptor +deactivate FilterCustomerDescriptor + +FilterCustomerCommandParser -> FilterCustomerDescriptor: getPredicate() +activate FilterCustomerDescriptor + +create BudgetAndTagsInRangePredicate +FilterCustomerDescriptor -> BudgetAndTagsInRangePredicate: +activate BudgetAndTagsInRangePredicate + +BudgetAndTagsInRangePredicate --> FilterCustomerDescriptor: budgetAndTagsInRangePredicate +deactivate BudgetAndTagsInRangePredicate + +FilterCustomerDescriptor --> FilterCustomerCommandParser: budgetAndTagsInRangePredicate +deactivate FilterCustomerDescriptor + +create FilterCustomerCommand +FilterCustomerCommandParser -> FilterCustomerCommand : +activate FilterCustomerCommand + +FilterCustomerCommand --> FilterCustomerCommandParser : command +deactivate FilterCustomerCommand + +FilterCustomerCommandParser --> AddressBookParser : command +deactivate FilterCustomerCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +FilterCustomerCommandParser -[hidden]-> AddressBookParser +destroy FilterCustomerCommandParser + +AddressBookParser --> LogicManager : command +deactivate AddressBookParser + +LogicManager -> FilterCustomerCommand : execute() +activate FilterCustomerCommand + +FilterCustomerCommand -> Model : updateCustList(budgetAndTagsInRangePredicate) + +create CommandResult +FilterCustomerCommand -> CommandResult : +activate CommandResult + +CommandResult --> FilterCustomerCommand +deactivate CommandResult + +FilterCustomerCommand --> LogicManager : commandResult +deactivate FilterCustomerCommand + +[<--LogicManager : commandResult +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FindCustomerSequenceDiagram.puml b/docs/diagrams/FindCustomerSequenceDiagram.puml new file mode 100644 index 00000000000..392ef85feb5 --- /dev/null +++ b/docs/diagrams/FindCustomerSequenceDiagram.puml @@ -0,0 +1,65 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindCustomerCommandParser" as FindCustomerCommandParser LOGIC_COLOR +participant "command:FindCustomerCommand" as FindCustomerCommand 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("findcust Jack") +activate LogicManager + +LogicManager -> AddressBookParser : parse("findcust Jack") +activate AddressBookParser + +create FindCustomerCommandParser +AddressBookParser -> FindCustomerCommandParser : +activate FindCustomerCommandParser + +FindCustomerCommandParser --> AddressBookParser +deactivate FindCustomerCommandParser + +AddressBookParser -> FindCustomerCommandParser : parse("Jack") +activate FindCustomerCommandParser + +create FindCustomerCommand +FindCustomerCommandParser -> FindCustomerCommand : +activate FindCustomerCommand + +FindCustomerCommand --> FindCustomerCommandParser : command +deactivate FindCustomerCommand + +FindCustomerCommandParser --> AddressBookParser : command +deactivate FindCustomerCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +FindCustomerCommandParser -[hidden]-> AddressBookParser +destroy FindCustomerCommandParser + +AddressBookParser --> LogicManager : command +deactivate AddressBookParser + +LogicManager -> FindCustomerCommand : execute() +activate FindCustomerCommand + +FindCustomerCommand -> Model : updateCustList(predicate) + +create CommandResult +FindCustomerCommand -> CommandResult : +activate CommandResult + +CommandResult --> FindCustomerCommand +deactivate CommandResult + +FindCustomerCommand --> LogicManager +deactivate FindCustomerCommand + +[<--LogicManager : commandResult +deactivate LogicManager +@enduml diff --git a/docs/diagrams/MatchCustomerSequenceDiagram.puml b/docs/diagrams/MatchCustomerSequenceDiagram.puml new file mode 100644 index 00000000000..5e77156e613 --- /dev/null +++ b/docs/diagrams/MatchCustomerSequenceDiagram.puml @@ -0,0 +1,67 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":MatchCustomerCommandParser" as MatchCustomerCommandParser LOGIC_COLOR +participant "command:MatchCustomerCommand" as MatchCustomerCommand 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("matchcust 1") +activate LogicManager + +LogicManager -> AddressBookParser : parse("matchcust 1") +activate AddressBookParser + +create MatchCustomerCommandParser +AddressBookParser -> MatchCustomerCommandParser : +activate MatchCustomerCommandParser + +MatchCustomerCommandParser --> AddressBookParser +deactivate MatchCustomerCommandParser + +AddressBookParser -> MatchCustomerCommandParser : parse("1") +activate MatchCustomerCommandParser + +create MatchCustomerCommand +MatchCustomerCommandParser -> MatchCustomerCommand : +activate MatchCustomerCommand + +MatchCustomerCommand --> MatchCustomerCommandParser : command +deactivate MatchCustomerCommand + +MatchCustomerCommandParser --> AddressBookParser : command +deactivate MatchCustomerCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +MatchCustomerCommandParser -[hidden]-> AddressBookParser +destroy MatchCustomerCommandParser + +AddressBookParser --> LogicManager : command +deactivate AddressBookParser + +LogicManager -> MatchCustomerCommand : execute() +activate MatchCustomerCommand + +MatchCustomerCommand -> Model : updateCustList() + +MatchCustomerCommand -> Model : updatePropList(ALL_PROPS) + +create CommandResult +MatchCustomerCommand -> CommandResult : +activate CommandResult + +CommandResult --> MatchCustomerCommand +deactivate CommandResult + +MatchCustomerCommand --> LogicManager : commandResult +deactivate MatchCustomerCommand + +[<--LogicManager : commandResult +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..e51efb215d7 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -12,9 +12,9 @@ Class AddressBook Class ModelManager Class UserPrefs -Class UniquePersonList -Class Person -Class Address +Class UniqueCustomerList +Class Customer +Class Budget Class Email Class Name Class Phone @@ -35,20 +35,20 @@ ModelManager -left-> "1" AddressBook ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +AddressBook *--> "1" UniqueCustomerList +UniqueCustomerList --> "~* all" Customer +Customer *--> Name +Customer *--> Phone +Customer *--> Email +Customer *--> Budget +Customer *--> "*" Tag -Person -[hidden]up--> I -UniquePersonList -[hidden]right-> I +Customer -[hidden]up--> I +UniqueCustomerList -[hidden]right-> I Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +Phone -[hidden]right-> Budget +Budget -[hidden]right-> Email -ModelManager --> "~* filtered" Person +ModelManager --> "~* filtered" Customer @enduml diff --git a/docs/diagrams/PropertyObjectDiagram.puml b/docs/diagrams/PropertyObjectDiagram.puml new file mode 100644 index 00000000000..f38c15d2505 --- /dev/null +++ b/docs/diagrams/PropertyObjectDiagram.puml @@ -0,0 +1,27 @@ +@startuml +skinparam ActivityFontSize 15 +skinparam ArrowFontSize 12 + +package PropertyObjectDiagram { + Object ":Property" as Property { + } + + Object ":PropName" as PropName { + } + Object ":PropAddress" as PropAddress { + } + Object ":PropPhone" as PropPhone { + } + Object ":Price" as Price { + } + Object ":Tag" as Tag { + } +} + +PropName <-- Property : has < +PropAddress <-- Property : has < +PropPhone <-- Property : has < +Price <-- Property : has < +Tag <-- Property : has < + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..92e46daa801 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -18,7 +18,7 @@ package "AddressBook Storage" #F4F6F6{ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook -Class JsonAdaptedPerson +Class JsonAdaptedCustomer Class JsonAdaptedTag } @@ -37,7 +37,7 @@ Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedCustomer +JsonAdaptedCustomer --> "*" JsonAdaptedTag @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..51b1a0957a8 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -11,8 +11,8 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +Class CustomerListPanel +Class CustomerCard Class StatusBarFooter Class CommandBox } @@ -32,26 +32,26 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" CustomerListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow -PersonListPanel -down-> "*" PersonCard +CustomerListPanel -down-> "*" CustomerCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +CustomerListPanel --|> UiPart +CustomerCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart -PersonCard ..> Model +CustomerCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow +CustomerListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml index 42bf46d3ce8..e48ef8817ab 100644 --- a/docs/diagrams/tracing/LogicSequenceDiagram.puml +++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml @@ -14,7 +14,7 @@ create ecp abp -> ecp abp -> ecp ++: parse(arguments) create ec -ecp -> ec ++: index, editPersonDescriptor +ecp -> ec ++: index, editCustomerDescriptor ec --> ecp -- ecp --> abp --: command abp --> logic --: command diff --git a/docs/images/AddCustomerActivityDiagram.png b/docs/images/AddCustomerActivityDiagram.png new file mode 100644 index 00000000000..a993b300b26 Binary files /dev/null and b/docs/images/AddCustomerActivityDiagram.png differ diff --git a/docs/images/AddPropertyActivityDiagram.png b/docs/images/AddPropertyActivityDiagram.png new file mode 100644 index 00000000000..bb75b2390cd Binary files /dev/null and b/docs/images/AddPropertyActivityDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 37ad06a2803..dba0cbc352b 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 02a42e35e76..940ef4048d2 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/CustomerObjectDiagram.png b/docs/images/CustomerObjectDiagram.png new file mode 100644 index 00000000000..fba30e7edd7 Binary files /dev/null and b/docs/images/CustomerObjectDiagram.png differ diff --git a/docs/images/DeleteCustomerActivityDiagram.png b/docs/images/DeleteCustomerActivityDiagram.png new file mode 100644 index 00000000000..b4a4534af2d Binary files /dev/null and b/docs/images/DeleteCustomerActivityDiagram.png differ diff --git a/docs/images/DeleteCustomerSequenceDiagram.png b/docs/images/DeleteCustomerSequenceDiagram.png new file mode 100644 index 00000000000..caca071d3e8 Binary files /dev/null and b/docs/images/DeleteCustomerSequenceDiagram.png differ diff --git a/docs/images/DeletePropertySequenceDiagram.png b/docs/images/DeletePropertySequenceDiagram.png new file mode 100644 index 00000000000..ca4a4aa6fe1 Binary files /dev/null and b/docs/images/DeletePropertySequenceDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index e186f7ba096..81b4f043dcf 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/EditCustomerSequenceDiagram.png b/docs/images/EditCustomerSequenceDiagram.png new file mode 100644 index 00000000000..1dd33384e9f Binary files /dev/null and b/docs/images/EditCustomerSequenceDiagram.png differ diff --git a/docs/images/EditPropertySequenceDiagram.png b/docs/images/EditPropertySequenceDiagram.png new file mode 100644 index 00000000000..a95789b2333 Binary files /dev/null and b/docs/images/EditPropertySequenceDiagram.png differ diff --git a/docs/images/FilterCustomerSequenceDiagram.png b/docs/images/FilterCustomerSequenceDiagram.png new file mode 100644 index 00000000000..da71d4ca62c Binary files /dev/null and b/docs/images/FilterCustomerSequenceDiagram.png differ diff --git a/docs/images/FindCustomerSequenceDiagram.png b/docs/images/FindCustomerSequenceDiagram.png new file mode 100644 index 00000000000..26e44c726b5 Binary files /dev/null and b/docs/images/FindCustomerSequenceDiagram.png differ diff --git a/docs/images/Logo.png b/docs/images/Logo.png new file mode 100644 index 00000000000..f9d4ec6b2cc Binary files /dev/null and b/docs/images/Logo.png differ diff --git a/docs/images/MatchCustomerSequenceDiagram.png b/docs/images/MatchCustomerSequenceDiagram.png new file mode 100644 index 00000000000..6cac82634cf Binary files /dev/null and b/docs/images/MatchCustomerSequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index a19fb1b4ac8..a4edb8aa331 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/PropertyObjectDiagram.png b/docs/images/PropertyObjectDiagram.png new file mode 100644 index 00000000000..1862214cb43 Binary files /dev/null and b/docs/images/PropertyObjectDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 18fa4d0d51f..fdbe89a44e8 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..e1a9dd11a4c 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..e4253bb7337 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/chrainx.png b/docs/images/chrainx.png new file mode 100644 index 00000000000..42b878c1a7e Binary files /dev/null and b/docs/images/chrainx.png differ diff --git a/docs/images/ferdihs.png b/docs/images/ferdihs.png new file mode 100644 index 00000000000..eeddf1d371f Binary files /dev/null and b/docs/images/ferdihs.png differ diff --git a/docs/images/jianrong7.png b/docs/images/jianrong7.png new file mode 100644 index 00000000000..3ffa1cce795 Binary files /dev/null and b/docs/images/jianrong7.png differ diff --git a/docs/images/nicolengk.png b/docs/images/nicolengk.png new file mode 100644 index 00000000000..7c147c70f69 Binary files /dev/null and b/docs/images/nicolengk.png differ diff --git a/docs/images/saraozn.png b/docs/images/saraozn.png new file mode 100644 index 00000000000..003612d99ba Binary files /dev/null and b/docs/images/saraozn.png differ diff --git a/docs/images/startUi.png b/docs/images/startUi.png new file mode 100644 index 00000000000..200c6edfa12 Binary files /dev/null and b/docs/images/startUi.png differ diff --git a/docs/images/user-guide/CommandBox.png b/docs/images/user-guide/CommandBox.png new file mode 100644 index 00000000000..3f692fae760 Binary files /dev/null and b/docs/images/user-guide/CommandBox.png differ diff --git a/docs/images/user-guide/CustomerList.png b/docs/images/user-guide/CustomerList.png new file mode 100644 index 00000000000..2306a242b0b Binary files /dev/null and b/docs/images/user-guide/CustomerList.png differ diff --git a/docs/images/user-guide/HelpWindow.png b/docs/images/user-guide/HelpWindow.png new file mode 100644 index 00000000000..c110e603cbf Binary files /dev/null and b/docs/images/user-guide/HelpWindow.png differ diff --git a/docs/images/user-guide/PropertyList.png b/docs/images/user-guide/PropertyList.png new file mode 100644 index 00000000000..0c5525bec82 Binary files /dev/null and b/docs/images/user-guide/PropertyList.png differ diff --git a/docs/images/user-guide/ReleasePage.png b/docs/images/user-guide/ReleasePage.png new file mode 100644 index 00000000000..75b19ee62eb Binary files /dev/null and b/docs/images/user-guide/ReleasePage.png differ diff --git a/docs/images/user-guide/Ui.png b/docs/images/user-guide/Ui.png new file mode 100644 index 00000000000..bc6a9df8916 Binary files /dev/null and b/docs/images/user-guide/Ui.png differ diff --git a/docs/images/user-guide/UserInterface.png b/docs/images/user-guide/UserInterface.png new file mode 100644 index 00000000000..99cd336ebf9 Binary files /dev/null and b/docs/images/user-guide/UserInterface.png differ diff --git a/docs/images/user-guide/addcustUi.png b/docs/images/user-guide/addcustUi.png new file mode 100644 index 00000000000..39276bd5ba1 Binary files /dev/null and b/docs/images/user-guide/addcustUi.png differ diff --git a/docs/images/user-guide/delcustUi.png b/docs/images/user-guide/delcustUi.png new file mode 100644 index 00000000000..4f73f6c136c Binary files /dev/null and b/docs/images/user-guide/delcustUi.png differ diff --git a/docs/images/user-guide/editcustUi.png b/docs/images/user-guide/editcustUi.png new file mode 100644 index 00000000000..b00145b68e0 Binary files /dev/null and b/docs/images/user-guide/editcustUi.png differ diff --git a/docs/images/user-guide/filtercustUi.png b/docs/images/user-guide/filtercustUi.png new file mode 100644 index 00000000000..11b52f5b957 Binary files /dev/null and b/docs/images/user-guide/filtercustUi.png differ diff --git a/docs/images/user-guide/findcustUi.png b/docs/images/user-guide/findcustUi.png new file mode 100644 index 00000000000..87a8403d6e4 Binary files /dev/null and b/docs/images/user-guide/findcustUi.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..84fcad8c9d8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,17 @@ --- layout: page -title: AddressBook Level-3 +title: PropertyMatch --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S1-CS2103T-W11-2/tp/actions) +[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/AY2324S1-CS2103T-W11-2/tp) ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +**PropertyMatch is a desktop application for property agents who want to organise their client profiles with their corresponding properties.** 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 PropertyMatch, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing PropertyMatch, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/chrainx.md b/docs/team/chrainx.md new file mode 100644 index 00000000000..fb75b174ea4 --- /dev/null +++ b/docs/team/chrainx.md @@ -0,0 +1,51 @@ +--- +layout: page +title: Fredy Lawrence's Project Portfolio Page +--- + +### Project: PropertyMatch + +PropertyMatch is a desktop tool for property agents who want to organise their client profiles with their corresponding properties. Property agents can boost their efficiency by seamlessly matching their clients with their desired properties. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about [NUMBER] kLoC. + +Given below are my contributions to the project. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=chrainx&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=Chrainx&tabRepo=AY2324S1-CS2103T-W11-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +* **Enhancements to existing features**: + * Added `addprop` commmand + * [#59](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/59) + * What it does: It takes in a command in the format `addprop n/NAME a/ADDRESS p/PHONE pr/PRICE [c/TAG]…​` and add that property to the database. + Where `NAME` represents the name, 'ADDRESS' represents the address, `PHONE` represents the phone number, `PRICE` represents the price, and `TAG` represent the properties' characteristic. + * Added `matchcust` command and tests + * [#108](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/108) + * What it does: It takes a command in the format `matchcust INDEX` and returns the properties which satisfy at least one characteristic of the selected customer (If any) and the price is affordable by the customer's budget. + Where `INDEX` represents number displayed in the UI besides the customer. + * Added `macthprop` command and tests + * [#108](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/108) + * What it does: It takes a command in the format `matchprop INDEX` and returns the customers which searching at least one characteristic of the selected properties (If any) and the price is affordable by the customer's budget. + Where `INDEX` represents number displayed in the UI besides the property. + +* **Contribution to the UG**: + * [#36](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/36) + * [#122](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/122) + * Added and updated `addprop`, `matchcust`, and `matchprop` description. + * Adjusted the command summary + * Reordering the UG + +* **Contribution to the DG**: + * [#37](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/37) + * [#99](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/99) + * Added user stories. + * Added and updated description and sequence diagram for `matchcust` and `matchprop` command. + +* **Review/mentoring contributions**: + * Reviewed 16 PRs (Links to some PR reviews below) + * [#23](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/23) + * [#68](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/68) + * [#113](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/113) + * Consistently answered queries from team members in the team chat + +* **Contributions beyond the project team**: + * Did the product pitch for CS2101 + * Reported 8 bugs for other team during PED diff --git a/docs/team/ferdihs.md b/docs/team/ferdihs.md new file mode 100644 index 00000000000..231cafd96af --- /dev/null +++ b/docs/team/ferdihs.md @@ -0,0 +1,51 @@ +--- +layout: page +title: Ferdinand Halim's Project Portfolio Page +--- + +### Project: PropertyMatch + +PropertyMatch is a desktop tool for property agents who want to organise their client profiles with their corresponding properties. Property agents can boost their efficiency by seamlessly matching their clients with their desired properties. + +Given below are my contributions to the project. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=FerdiHS&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=FerdiHS&tabRepo=AY2324S1-CS2103T-W11-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +* **Enhancements implemented**: + * Added `addcust` commmand and tests + * [#56](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/56) + * [#64](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/64) + * What it does: It takes in a command in the format `addcust n/NAME p/PHONE e/EMAIL b/BUDGET [c/CHARACTERISTIC]…​` and add that customer to the database. + Where `NAME` represents the name, `PHONE` represents the phone number, `EMAIL` represents the email, `BUDGET` represents the budget, and `CHARACTERISTIC` represent the properties' characteristic wanted by the customer. + * Added `filtercust` command and tests + * [#102](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/102) + * [#117](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/117) + * What it does: It takes a command in the format `filtercust [b/BUDGET] [c/CHARACTERISTIC]…​` and returns the customers who satisfy the predicate. + Where `BUDGET` represents the minimum budget of the filtered customers and `CHARACTERISTIC` represents the minimum set of characteristics the filtered customers wanted. + * Added `filterprop` command and tests + * [#107](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/107) + * [#102](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/102) + * What it does: It takes a command in the format `filterprop [pr/PRICE] [c/CHARACTERISTIC]…​` and returns the properties who satisfy the predicate. + Where `PRICE` represents the minimum price of the filtered properties and `CHARACTERISTIC` represents the minimum set of characteristics the filtered properties wanted. + +* **Contribution to the UG**: + * [#26](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/26) + * [#102](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/102) + * Added and updated `addcust`, `filtercust`, and `filterprop` description. + +* **Contribution to the DG**: + * [#102](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/102) + * [#25](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/25) + * Added user stories. + * Added and updated description and sequence diagram for `filtercust` and `filterprop` command. + +* **Review/mentoring contributions**: + * Reviewed 16 PRs (Links to some PR reviews below) + * [#98](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/98) + * [#110](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/110) + * [#120](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/120) + * Consistently answered queries from team members in the team chat + +* **Contributions beyond the project team**: + * Did the product pitch for CS2101 + * Reported 10 bugs for other team during PED diff --git a/docs/team/jianrong7.md b/docs/team/jianrong7.md new file mode 100644 index 00000000000..46a41893cd3 --- /dev/null +++ b/docs/team/jianrong7.md @@ -0,0 +1,72 @@ +--- +layout: page +title: Loh Jian Rong's Project Portfolio Page +--- + +### Project: PropertyMatch + +PropertyMatch is a desktop tool for property agents who want to organise their client profiles with their corresponding properties. Property agents can boost their efficiency by seamlessly matching their clients with their desired properties. + +Given below are my contributions to the project. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=jianrong7&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=jianrong7&tabRepo=AY2324S1-CS2103T-W11-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +* **Enhancements implemented**: + * Added `listprop` command and tests ([#66](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/66), [#84](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/84)) + * What it does: Takes in command `listprop` and updates the Property List to show all properties in the database. + * Added goodbye message to `exit` command ([#58](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/58)) + * What it does: Takes in command `exit` and displays a goodbye message. Delays the exit by 3 seconds to give the user sufficient time to read the message. + * Added `editcust` command and tests ([#110](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/110)) + * What it does: Takes in command `editcust`. Allows the user to edit an existing customer. However, the user cannot edit the customer such that it has a duplicate phone number. + * Added `clear` command and tests ([#120](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/120)) + * What it does: Takes in command `clear` and removes all existing data, both customer list and property list, in the application. + * Removed traces of AB3 ([#61](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/61)) + * Fix CI/CD pipeline errors ([#94](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/94)) + * Added guardrails for phone numbers, budgets and prices ([#187](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/187), [#188](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/188)) + * What it does: Ensures that phone numbers, budgets and prices are valid. Phone numbers must start with 6, 8, or 9 and must be 8 digits. Budget and prices must be integers of at least 5 digits and at most 12 digits. It cannot start with a 0. + + +* **Contributions to the UG**: + * Make UG more friendly to property agents using our application in these PRs ([#121](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/121), [#167](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/167), [#174](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/174)) + + +* **Contributions to the DG**: + * Added sequence diagram for edit command in [#97](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/97) + * Massive change to DG in v1.4 [#229](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/229) + * Added object diagrams for Customer and Property + * Added activity diagrams for `addcust` and `addprop` commands + * Added Appendix B: Instructions for manual testing + * Added some proposed enhancements in Appendix C + * Added Appendix D: Effort + + +* **Project management**: + * Led weekly group meetings + * Managed milestones `v1.2` - `v1.4` (4 milestones) on GitHub + * Managed releases `v1.2` - `v1.4` (4 releases) on GitHub + + +* **Contributions to team-based tasks**: + * Setting up the GitHub team org and repo + * Maintaining the issue tracker + * Release management + * Updating user guide that are not specific to feature + * Updating developer guide that are not specific to feature + * Set up CI for the team repository + * Enabled assertions in Gradle + * Filmed product demo for v1.2 and v1.3 + + +* **Review/mentoring contributions**: + * Reviewed 16 PRs (Links to PR reviews with non-trivial comments below) + * [#56](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/56), [#64](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/64), [#87](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/87), [#93](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/93), [#122](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/122), [#173](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/173) + * Consistently answered queries from team members in the team chat + + +* **Contributions beyond the project team**: + * Did the product pitch for CS2101 + * Reported 10 bugs for other team during PED + * Answered queries in the CS2103T forum (Links to answers) + * [#1](https://github.com/nus-cs2103-AY2324S1/forum/issues/107#issuecomment-1706281857) + * [#2](https://github.com/nus-cs2103-AY2324S1/forum/issues/92#issuecomment-1704647357) diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md index 773a07794e2..e9679f46705 100644 --- a/docs/team/johndoe.md +++ b/docs/team/johndoe.md @@ -5,7 +5,7 @@ title: John Doe's Project Portfolio Page ### Project: AddressBook Level 3 -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. +AddressBook - Level 3 is a desktop propAddress book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. Given below are my contributions to the project. diff --git a/docs/team/nicolengk.md b/docs/team/nicolengk.md new file mode 100644 index 00000000000..645cf8de4a5 --- /dev/null +++ b/docs/team/nicolengk.md @@ -0,0 +1,64 @@ +--- +layout: page +title: Nicole Ng's Project Portfolio Page +--- + +### Project: PropertyMatch + +PropertyMatch is a desktop tool for property agents who want to organise their client profiles with their corresponding properties. Property agents can boost their efficiency by seamlessly matching their clients with their desired properties. + +Given below are my contributions to the project. + +* **Enhancements implemented**: + * Added `delprop` command and tests + * [#67](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/67) + * What it does: It takes in a command in the format `delprop INDEX` where `INDEX` refers to the index number shown in the displayed property list + + * Added `listcust` command and tests + * [#69](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/69) + * [#86](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/86) + * What it does: Updates the Customer List to show all customers in the database + + * Added `findcust` command and tests + * [#112](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/112) + + * What it does: It takes in a command in the format `findcust NAME` where `NAME` represents the substring to search for. When executed, the findcust commands scans the customer address book for customers whose names begins with the `NAME` substring at any position within their name. The results are then returned. + + * Justification: The `findcust` feature enhances user convenience and improve customer management within the application. It provides a convenient way to quickly locate specific customers based on a partial name match. + + * Highlights: + * Flexible Substring Matching: It allows for substring matching at any position within the customer's name, offering flexibility in identifying customers with diverse name structures. + + * Added `findprop` command and tests + * [#113](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/113) + + * What it does: It takes in a command in the format `findprop NAME` where `NAME` represents the substring to search for. When executed, the `findprop` commands scans the property address book for properties whose names begins with the `NAME` substring at any position within their name. The results are then returned. + + * Justification: The `findprop` feature enhances user convenience and improve property management within the application. It provides a convenient way to quickly locate specific properties based on a partial name match. + + * Highlights: + * Flexible Substring Matching: It allows for substring matching at any position within the property's name, offering flexibility in identifying properties with diverse name structures. + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=nicolengk&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22) + +* **Contributions to the UG** + * Added `findcust` and `findprop` description [#101](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/101) + * Update overall ug format [#206](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/206) + +* **Contributions to the DG** + * [#101](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/101) + * Updated description for `delprop`, `delcust`, `findcust` and `findprop` + * Added sequence diagram for `findcust` + +* **Contributions to team-based tasks**: + * Added UI design [#16](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/16) + * Added use cases for DG [#41](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/41) + * Cleaning up UG bugs aft PED + +* **Review/mentoring contributions**: + * Reviewed 14 PRs (some reviewed PR links) + * [#59](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/59) + * [#102](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/102) + * [#114](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/114) + diff --git a/docs/team/saraozn.md b/docs/team/saraozn.md new file mode 100644 index 00000000000..39049fc84db --- /dev/null +++ b/docs/team/saraozn.md @@ -0,0 +1,44 @@ +--- +layout: page +title: Sara Ong's Project Portfolio Page +--- + +### Project: PropertyMatch + +PropertyMatch is a desktop tool for property agents who want to organise their client profiles with their corresponding properties. Property agents can boost their efficiency by seamlessly matching their clients with their desired properties. + +Given below are my contributions to the project. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=saraozn&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&zFR=false&tabAuthor=saraozn&tabRepo=AY2324S1-CS2103T-W11-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +* **Enhancements implemented**: + * Added `delcust` command and tests ([#68](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/68)) + * What it does: Allows user to delete a customer from the database when the details of this customer is not needed anymore, so that the database will not be populated with too many irrelevant details. + * Added `editprop` command and tests ([#93](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/93)) + * What it does: Allows user to edit the individual details of an exiting property without having to delete and re-add the property. + * Added `addprop` tests ([#87](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/87)) + * Test cases used for testing `addprop` + +* **Contributions to the UG**: + * Updated UG for `delcust` command ([#17](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/17)) + * Updated UG for `editcust` and `editprop` commands ([#106](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/106)) + * Standardise format used in the UG ([#165](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/165)) + * Fix documentation bugs ([#180](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/180), [#233](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/233)) + * Add and update UI screenshots to UG ([#189](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/189)) + * Update UG to inform users of feature change ([#202](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/202)) + * Vet and fix small bugs throughout the whole UG ([#209](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/209), [#236](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/236)) + +* **Contributions to the DG**: + * Added sequence diagram for edit and delete command ([#106](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/106)) + * Updated use cases and add extensions ([#240](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/240)) + +* **Contributions to team-based tasks**: + * Updated landing page with new UI ([#115](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/115)) + * Updating user guide that are not specific to feature. + +* **Contributions beyond the project team**: + * Reported bugs for other team's product during PED + +* **Review contributions**: + * Reviewed 17 PRs (Links to some PR reviews) + * [#108](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/108), [#122](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/122), [#181](https://github.com/AY2324S1-CS2103T-W11-2/tp/pull/181) diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md index d98f38982e7..a9ed287598d 100644 --- a/docs/tutorials/AddRemark.md +++ b/docs/tutorials/AddRemark.md @@ -28,7 +28,7 @@ package seedu.address.logic.commands; import seedu.address.model.Model; /** - * Changes the remark of an existing person in the address book. + * Changes the remark of an existing customer in the address book. */ public class RemarkCommand extends Command { @@ -65,8 +65,8 @@ Following the convention in other commands, we add relevant messages as constant ``` java public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Edits the remark of the person identified " - + "by the index number used in the last person listing. " + + ": Edits the remark of the customer identified " + + "by the index number used in the last customer listing. " + "Existing remark will be overwritten by the input.\n" + "Parameters: INDEX (must be a positive integer) " + "r/ [REMARK]\n" @@ -101,8 +101,8 @@ public class RemarkCommand extends Command { private final String remark; /** - * @param index of the person in the filtered person list to edit the remark - * @param remark of the person to be updated to + * @param index of the customer in the filtered customer list to edit the remark + * @param remark of the customer to be updated to */ public RemarkCommand(Index index, String remark) { requireAllNonNull(index, remark); @@ -223,11 +223,11 @@ If you are stuck, check out the sample ## Add `Remark` to the model -Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of person data. We achieve that by working with the `Person` model. Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person. +Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of customer data. We achieve that by working with the `Person` model. Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the customer’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a customer. ### Add a new `Remark` class -Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. +Create a new `Remark` in `seedu.address.model.customer`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-41bb13c581e280c686198251ad6cc337cd5e27032772f06ed9bf7f1440995ece). Note how `Remark` has no constrains and thus does not require input validation. @@ -238,9 +238,9 @@ Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark` ## Add a placeholder element for remark to the UI -Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person. +Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each customer. -Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688). +Simply add the following to [`seedu.address.ui.CustomerCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688). **`PersonCard.java`:** @@ -309,9 +309,9 @@ Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/c **`PersonCard.java`:** ``` java -public PersonCard(Person person, int displayedIndex) { +public PersonCard(Person customer, int displayedIndex) { //... - remark.setText(person.getRemark().value); + remark.setText(customer.getRemark().value); } ``` @@ -341,25 +341,25 @@ save it with `Model#setPerson()`. throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = new Person( - personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), - personToEdit.getAddress(), remark, personToEdit.getTags()); + Person customerToEdit = lastShownList.get(index.getZeroBased()); + Person editedCustomer = new Person( + customerToEdit.getName(), customerToEdit.getPhone(), customerToEdit.getEmail(), + customerToEdit.getAddress(), remark, customerToEdit.getTags()); - model.setPerson(personToEdit, editedPerson); + model.setPerson(customerToEdit, editedCustomer); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(generateSuccessMessage(editedPerson)); + return new CommandResult(generateSuccessMessage(editedCustomer)); } /** * Generates a command execution success message based on whether * the remark is added to or removed from - * {@code personToEdit}. + * {@code customerToEdit}. */ - private String generateSuccessMessage(Person personToEdit) { + private String generateSuccessMessage(Person customerToEdit) { String message = !remark.value.isEmpty() ? MESSAGE_ADD_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS; - return String.format(message, personToEdit); + return String.format(message, customerToEdit); } ``` diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md index f29169bc924..be7b2cd4905 100644 --- a/docs/tutorials/RemovingFields.md +++ b/docs/tutorials/RemovingFields.md @@ -28,7 +28,7 @@ IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a re ### Assisted refactoring -The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu. +The `address` field in `Person` is actually an instance of the `seedu.address.model.customer.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu. * :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences` ![Usages detected](../images/remove/UnsafeDelete.png) @@ -100,7 +100,7 @@ In `src/test/data/`, data meant for testing purposes are stored. While keeping t ```json { - "persons": [ { + "customers": [ { "name": "Person with invalid name field: Ha!ns Mu@ster", "phone": "9482424", "email": "hans@example.com", diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md index 4fb62a83ef6..b7f58b42f05 100644 --- a/docs/tutorials/TracingCode.md +++ b/docs/tutorials/TracingCode.md @@ -60,7 +60,7 @@ public interface Logic { * @throws ParseException If an error occurs during parsing. */ CommandResult execute(String commandText) throws CommandException, ParseException; -... + ... } ``` @@ -189,22 +189,22 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ @Override public CommandResult execute(Model model) throws CommandException { ... - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + Person customerToEdit = lastShownList.get(index.getZeroBased()); + Person editedCustomer = createEditedPerson(customerToEdit, editCustomerDescriptor); + if (!customerToEdit.isSamePerson(editedCustomer) && model.hasPerson(editedCustomer)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } - model.setPerson(personToEdit, editedPerson); + model.setPerson(customerToEdit, editedCustomer); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedCustomer)); } ``` 1. As suspected, `command#execute()` does indeed make changes to the `model` object. Specifically, - * it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the person data. - * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
- FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
- To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked. + * it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the customer data. + * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ customers.
+ FYI, The 'filtered list' is the list of customers resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the customers so that the user can see the edited customer along with all other customers. If this was a `find` command, we would be setting that list to contain the search results instead.
+ To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of customers is being tracked.
* :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component) @@ -231,7 +231,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ * {@code JsonSerializableAddressBook}. */ public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll( + customers.addAll( source.getPersonList() .stream() .map(JsonAdaptedPerson::new) diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 3d6bd06d5af..01ae1d0c685 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -18,13 +18,17 @@ import seedu.address.model.AddressBook; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import seedu.address.model.PropertyBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyPropertyBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; import seedu.address.model.util.SampleDataUtil; import seedu.address.storage.AddressBookStorage; import seedu.address.storage.JsonAddressBookStorage; +import seedu.address.storage.JsonPropertyBookStorage; import seedu.address.storage.JsonUserPrefsStorage; +import seedu.address.storage.PropertyBookStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; @@ -36,7 +40,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 2, true); + public static final Version VERSION = new Version(1, 4, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -48,7 +52,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing PropertyMatch ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -58,7 +62,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + PropertyBookStorage propertyBookStorage = new JsonPropertyBookStorage(userPrefs.getPropertyBookFilePath()); + storage = new StorageManager(addressBookStorage, propertyBookStorage, userPrefsStorage); model = initModelManager(storage, userPrefs); @@ -76,10 +81,13 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { logger.info("Using data file : " + storage.getAddressBookFilePath()); Optional addressBookOptional; + Optional propertyBookOptional; ReadOnlyAddressBook initialData; + ReadOnlyPropertyBook initialProperty; try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { + logger.info("Data file " + storage.getAddressBookFilePath() + " not found."); logger.info("Creating a new data file " + storage.getAddressBookFilePath() + " populated with a sample AddressBook."); } @@ -90,7 +98,20 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { initialData = new AddressBook(); } - return new ModelManager(initialData, userPrefs); + try { + propertyBookOptional = storage.readPropertyBook(); + if (!propertyBookOptional.isPresent()) { + logger.info("Creating a new data file " + storage.getPropertyBookFilePath() + + " populated with a sample PropertyBook."); + } + initialProperty = propertyBookOptional.orElseGet(SampleDataUtil::getSamplePropertyBook); + } catch (DataLoadingException e) { + logger.warning("Data file at " + storage.getPropertyBookFilePath() + " could not be loaded." + + " Will be starting with an empty PropertyBook."); + initialProperty = new PropertyBook(); + } + + return new ModelManager(initialData, initialProperty, userPrefs); } private void initLogging(Config config) { @@ -170,13 +191,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting PropertyMatch " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping PropertyMatch ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..36945e7bdcf 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -38,6 +38,53 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Returns true if the {@code sentence} contains the {@code word}. + * Ignores case and a full word match is NOT required. + *
examples:
+     *       containsWordIgnoreCase("ABc def", "abc") == true
+     *       containsWordIgnoreCase("ABc def", "DEF") == true
+     *       containsWordIgnoreCase("ABc def", "AB") == true // full word match not needed
+     *       
+ * @param sentence cannot be null + * @param word cannot be null, cannot be empty, must be a single word + */ + public static boolean containsWordIgnoreCaseWithoutFullMatch(String sentence, String word) { + requireNonNull(sentence); + requireNonNull(word); + + String preppedWord = word.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[] wordsInPreppedSentence = preppedSentence.split("\\s+"); + + return Arrays.stream(wordsInPreppedSentence) + .anyMatch(w -> w.toLowerCase().contains(preppedWord.toLowerCase())); + } + + /** + * Returns true if the {@code sentence} start with the {@code word}. + * Ignores case and a full word match is NOT required. + * @param sentence cannot be null + * @param word cannot be null, cannot be empty, must be a single word + */ + public static boolean startsWithWordIgnoreCaseWithoutFullMatch(String sentence, String word) { + requireNonNull(sentence); + requireNonNull(word); + + String preppedWord = word.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[] wordsInPreppedSentence = preppedSentence.split("\\s+"); + + return Arrays.stream(wordsInPreppedSentence) + .anyMatch(w -> w.toLowerCase().startsWith(preppedWord.toLowerCase())); + } + /** * Returns a detailed message of the t, including the stack trace. */ @@ -50,7 +97,7 @@ public static String getDetails(Throwable t) { /** * Returns true if {@code s} represents a non-zero unsigned integer - * e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE}
+ * e.g. 1, 2, 3, ..., {@code Address.MAX_VALUE}
* Will return false for any other non-null string input * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) * @throws NullPointerException if {@code s} is null. @@ -60,7 +107,7 @@ public static boolean isNonZeroUnsignedInteger(String s) { try { int value = Integer.parseInt(s); - return value > 0 && !s.startsWith("+"); // "+1" is successfully parsed by Integer#parseInt(String) + return value > 0 && !s.startsWith("+"); // "+1" is successfully parsed by Address#parseInt(String) } catch (NumberFormatException nfe) { return false; } diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..6228aa9aae6 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,7 +8,9 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.ReadOnlyPropertyBook; +import seedu.address.model.customer.Customer; +import seedu.address.model.property.Property; /** * API of the Logic component @@ -30,14 +32,28 @@ public interface Logic { */ ReadOnlyAddressBook getAddressBook(); + /** Returns an unmodifiable view of the filtered list of customers */ + ObservableList getFilteredCustomerList(); + + /** + * Returns the AddressBook. + * + * @see seedu.address.model.Model#getPropertyBook() + */ + ReadOnlyPropertyBook getPropertyBook(); /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + ObservableList getFilteredPropertyList(); /** * Returns the user prefs' address book file path. */ Path getAddressBookFilePath(); + /** + * Returns the user prefs' property book file path. + */ + Path getPropertyBookFilePath(); + /** * Returns the user prefs' GUI settings. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 5aa3b91c7d0..165659276ef 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -15,7 +15,9 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.ReadOnlyPropertyBook; +import seedu.address.model.customer.Customer; +import seedu.address.model.property.Property; import seedu.address.storage.Storage; /** @@ -33,6 +35,7 @@ public class LogicManager implements Logic { private final Storage storage; private final AddressBookParser addressBookParser; + /** * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. */ @@ -52,6 +55,7 @@ public CommandResult execute(String commandText) throws CommandException, ParseE try { storage.saveAddressBook(model.getAddressBook()); + storage.savePropertyBook(model.getPropertyBook()); } catch (AccessDeniedException e) { throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e); } catch (IOException ioe) { @@ -67,8 +71,18 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ReadOnlyPropertyBook getPropertyBook() { + return model.getPropertyBook(); + } + + + public ObservableList getFilteredCustomerList() { + return model.getFilteredCustomerList(); + } + + @Override + public ObservableList getFilteredPropertyList() { + return model.getFilteredPropertyList(); } @Override @@ -76,6 +90,12 @@ public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); } + @Override + public Path getPropertyBookFilePath() { + return model.getPropertyBookFilePath(); + } + + @Override public GuiSettings getGuiSettings() { return model.getGuiSettings(); diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..356c9e5bebc 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -5,7 +5,9 @@ import java.util.stream.Stream; import seedu.address.logic.parser.Prefix; -import seedu.address.model.person.Person; +import seedu.address.model.customer.Customer; +import seedu.address.model.property.Property; + /** * Container for user visible messages. @@ -14,8 +16,16 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_CUSTOMER_DISPLAYED_INDEX = "The customer index provided is invalid"; + + public static final String MESSAGE_INVALID_PROPERTY_DISPLAYED_INDEX = "The property index provided is invalid"; + + public static final String MESSAGE_CUSTOMERS_MATCH_OVERVIEW = "%1$d properties matched with customer "; + + public static final String MESSAGE_PROPERTIES_MATCH_OVERVIEW = "%1$d customers matched with property "; + public static final String MESSAGE_CUSTOMERS_LISTED_OVERVIEW = "%1$d customers listed!"; + + public static final String MESSAGE_PROPERTIES_LISTED_OVERVIEW = "%1$d properties listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = "Multiple values specified for the following single-valued field(s): "; @@ -32,20 +42,36 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref } /** - * Formats the {@code person} for display to the user. + * Formats the {@code customer} for display to the user. */ - public static String format(Person person) { + public static String format(Customer customer) { final StringBuilder builder = new StringBuilder(); - builder.append(person.getName()) + builder.append(customer.getName()) .append("; Phone: ") - .append(person.getPhone()) + .append(customer.getPhone()) .append("; Email: ") - .append(person.getEmail()) - .append("; Address: ") - .append(person.getAddress()) + .append(customer.getEmail()) + .append("; Budget: ") + .append(customer.getBudget()) .append("; Tags: "); - person.getTags().forEach(builder::append); + customer.getTags().forEach(builder::append); return builder.toString(); } + /** + * Formats the {@code property} for display to the user. + */ + public static String format(Property property) { + final StringBuilder builder = new StringBuilder(); + builder.append(property.getName()) + .append("; Address: ") + .append(property.getAddress()) + .append("; Phone: ") + .append(property.getPhone()) + .append("; Price: ") + .append(property.getPrice()) + .append("; Tags: "); + property.getTags().forEach(builder::append); + return builder.toString(); + } } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCustomerCommand.java similarity index 55% rename from src/main/java/seedu/address/logic/commands/AddCommand.java rename to src/main/java/seedu/address/logic/commands/AddCustomerCommand.java index 5d7185a9680..d5cbacea87d 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCustomerCommand.java @@ -1,7 +1,7 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BUDGET; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; @@ -11,52 +11,52 @@ import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.customer.Customer; /** - * Adds a person to the address book. + * Adds a customer to the address book. */ -public class AddCommand extends Command { +public class AddCustomerCommand extends Command { - public static final String COMMAND_WORD = "add"; + public static final String COMMAND_WORD = "addcust"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a customer to the address book. " + "Parameters: " + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " + + PREFIX_PHONE + "PHONE_NUMBER " + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_BUDGET + "BUDGET " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_BUDGET + "100000000 " + + PREFIX_TAG + "bright " + + PREFIX_TAG + "sunny"; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_SUCCESS = "New customer added: %1$s"; + public static final String MESSAGE_DUPLICATE_CUSTOMER = "This customer already exists in the address book"; - private final Person toAdd; + private final Customer toAdd; /** - * Creates an AddCommand to add the specified {@code Person} + * Creates an AddCustomerCommand to add the specified {@code Customer} */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; + public AddCustomerCommand(Customer customer) { + requireNonNull(customer); + toAdd = customer; } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + if (model.hasCustomer(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_CUSTOMER); } - model.addPerson(toAdd); + model.addCustomer(toAdd); return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); } @@ -67,12 +67,12 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof AddCommand)) { + if (!(other instanceof AddCustomerCommand)) { return false; } - AddCommand otherAddCommand = (AddCommand) other; - return toAdd.equals(otherAddCommand.toAdd); + AddCustomerCommand otherAddCustomerCommand = (AddCustomerCommand) other; + return toAdd.equals(otherAddCustomerCommand.toAdd); } @Override diff --git a/src/main/java/seedu/address/logic/commands/AddPropertyCommand.java b/src/main/java/seedu/address/logic/commands/AddPropertyCommand.java new file mode 100644 index 00000000000..6f649ab1f55 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddPropertyCommand.java @@ -0,0 +1,84 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +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.property.Property; + +/** + * Adds a Property to the address book. + */ +public class AddPropertyCommand extends Command { + + public static final String COMMAND_WORD = "addprop"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a property to the PropertyMatch. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_PHONE + "PHONE_NUMBER " + + PREFIX_PRICE + "PRICE " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "Kuantan Regency " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_PHONE + "98765432 " + + PREFIX_PRICE + "100000000 " + + PREFIX_TAG + "bright " + + PREFIX_TAG + "sunny"; + + public static final String MESSAGE_SUCCESS = "New property added: %1$s"; + public static final String MESSAGE_DUPLICATE_PROPERTY = "This property already exists in the PropertyMatch"; + + private final Property toAdd; + + /** + * Creates an AddPropertyCommand to add the specified {@code Property} + */ + public AddPropertyCommand(Property property) { + requireNonNull(property); + toAdd = property; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasProperty(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PROPERTY); + } + + model.addProperty(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddPropertyCommand)) { + return false; + } + + AddPropertyCommand otherAddPropertyCommand = (AddPropertyCommand) other; + return toAdd.equals(otherAddPropertyCommand.toAdd); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..9eac2c53e73 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -4,6 +4,7 @@ import seedu.address.model.AddressBook; import seedu.address.model.Model; +import seedu.address.model.PropertyBook; /** * Clears the address book. @@ -11,13 +12,14 @@ 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 = "Customer List and Property List have been cleared!"; @Override public CommandResult execute(Model model) { requireNonNull(model); model.setAddressBook(new AddressBook()); + model.setPropertyBook(new PropertyBook()); 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/DeleteCustomerCommand.java similarity index 57% rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java rename to src/main/java/seedu/address/logic/commands/DeleteCustomerCommand.java index 1135ac19b74..0d837f63915 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCustomerCommand.java @@ -9,40 +9,40 @@ import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.customer.Customer; /** - * Deletes a person identified using it's displayed index from the address book. + * Deletes a customer identified using it's displayed index from the address book. */ -public class DeleteCommand extends Command { +public class DeleteCustomerCommand extends Command { - public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_WORD = "delcust"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" + + ": Deletes the customer identified by the index number used in the displayed customer list.\n" + "Parameters: INDEX (must be a positive integer)\n" + "Example: " + COMMAND_WORD + " 1"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_CUSTOMER_SUCCESS = "Deleted Customer: %1$s"; private final Index targetIndex; - public DeleteCommand(Index targetIndex) { + public DeleteCustomerCommand(Index targetIndex) { this.targetIndex = targetIndex; } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getFilteredCustomerList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_CUSTOMER_DISPLAYED_INDEX); } - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); + Customer customerToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteCustomer(customerToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_CUSTOMER_SUCCESS, Messages.format(customerToDelete))); } @Override @@ -52,11 +52,11 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof DeleteCommand)) { + if (!(other instanceof DeleteCustomerCommand)) { return false; } - DeleteCommand otherDeleteCommand = (DeleteCommand) other; + DeleteCustomerCommand otherDeleteCommand = (DeleteCustomerCommand) other; return targetIndex.equals(otherDeleteCommand.targetIndex); } diff --git a/src/main/java/seedu/address/logic/commands/DeletePropertyCommand.java b/src/main/java/seedu/address/logic/commands/DeletePropertyCommand.java new file mode 100644 index 00000000000..852e03475ff --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeletePropertyCommand.java @@ -0,0 +1,69 @@ +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.property.Property; + +/** + * Deletes a property identified using it's displayed index from the address book. + */ +public class DeletePropertyCommand extends Command { + + public static final String COMMAND_WORD = "delprop"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the property identified by the index number used in the displayed property list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_PROPERTY_SUCCESS = "Deleted Property: %1$s"; + + private final Index targetIndex; + + public DeletePropertyCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPropertyList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PROPERTY_DISPLAYED_INDEX); + } + + Property propertyToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteProperty(propertyToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_PROPERTY_SUCCESS, Messages.format(propertyToDelete))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeletePropertyCommand)) { + return false; + } + + DeletePropertyCommand otherDeleteCommand = (DeletePropertyCommand) other; + return targetIndex.equals(otherDeleteCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCustomerCommand.java similarity index 50% rename from src/main/java/seedu/address/logic/commands/EditCommand.java rename to src/main/java/seedu/address/logic/commands/EditCustomerCommand.java index 4b581c7331e..7389a8dbed6 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCustomerCommand.java @@ -1,12 +1,12 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BUDGET; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CUSTOMERS; import java.util.Collections; import java.util.HashSet; @@ -21,87 +21,88 @@ import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.customer.Budget; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; import seedu.address.model.tag.Tag; /** - * Edits the details of an existing person in the address book. + * Edits the details of an existing customer in the budget book. */ -public class EditCommand extends Command { +public class EditCustomerCommand extends Command { - public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_WORD = "editcust"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the customer identified " + + "by the index number used in the displayed customer list. " + "Existing values will be overwritten by the input values.\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_BUDGET + "BUDGET] " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com"; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_EDIT_CUSTOMER_SUCCESS = "Edited Customer: %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_CUSTOMER = "This customer has a duplicate phone number."; private final Index index; - private final EditPersonDescriptor editPersonDescriptor; + private final EditCustomerDescriptor editCustomerDescriptor; /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with + * @param index of the customer in the filtered customer list to edit + * @param editCustomerDescriptor details to edit the customer with */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + public EditCustomerCommand(Index index, EditCustomerDescriptor editCustomerDescriptor) { requireNonNull(index); - requireNonNull(editPersonDescriptor); + requireNonNull(editCustomerDescriptor); this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); + this.editCustomerDescriptor = new EditCustomerDescriptor(editCustomerDescriptor); } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getFilteredCustomerList(); if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_CUSTOMER_DISPLAYED_INDEX); } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + Customer customerToEdit = lastShownList.get(index.getZeroBased()); + Customer editedCustomer = createEditedCustomer(customerToEdit, editCustomerDescriptor); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + if (!customerToEdit.isSameCustomer(editedCustomer) && model.hasCustomer(editedCustomer)) { + throw new CommandException(MESSAGE_DUPLICATE_CUSTOMER); } - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); + model.setCustomer(customerToEdit, editedCustomer); + model.updateFilteredCustomerList(PREDICATE_SHOW_ALL_CUSTOMERS); + return new CommandResult(String.format(MESSAGE_EDIT_CUSTOMER_SUCCESS, Messages.format(editedCustomer))); } /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. + * Creates and returns a {@code Customer} with the details of {@code customerToEdit} + * edited with {@code editCustomerDescriptor}. */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; + private static Customer createEditedCustomer(Customer customerToEdit, + EditCustomerDescriptor editCustomerDescriptor) { + assert customerToEdit != null; - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); + Name updatedName = editCustomerDescriptor.getName().orElse(customerToEdit.getName()); + Phone updatedPhone = editCustomerDescriptor.getPhone().orElse(customerToEdit.getPhone()); + Email updatedEmail = editCustomerDescriptor.getEmail().orElse(customerToEdit.getEmail()); + Budget updatedBudget = editCustomerDescriptor.getBudget().orElse(customerToEdit.getBudget()); + Set updatedTags = editCustomerDescriptor.getTags().orElse(customerToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Customer(updatedName, updatedPhone, updatedEmail, updatedBudget, updatedTags); } @Override @@ -111,45 +112,45 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof EditCommand)) { + if (!(other instanceof EditCustomerCommand)) { return false; } - EditCommand otherEditCommand = (EditCommand) other; - return index.equals(otherEditCommand.index) - && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor); + EditCustomerCommand otherEditCustomerCommand = (EditCustomerCommand) other; + return index.equals(otherEditCustomerCommand.index) + && editCustomerDescriptor.equals(otherEditCustomerCommand.editCustomerDescriptor); } @Override public String toString() { return new ToStringBuilder(this) .add("index", index) - .add("editPersonDescriptor", editPersonDescriptor) + .add("editCustomerDescriptor", editCustomerDescriptor) .toString(); } /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. + * Stores the details to edit the customer with. Each non-empty field value will replace the + * corresponding field value of the customer. */ - public static class EditPersonDescriptor { + public static class EditCustomerDescriptor { private Name name; private Phone phone; private Email email; - private Address address; + private Budget budget; private Set tags; - public EditPersonDescriptor() {} + public EditCustomerDescriptor() {} /** * Copy constructor. * A defensive copy of {@code tags} is used internally. */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { + public EditCustomerDescriptor(EditCustomerDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); - setAddress(toCopy.address); + setBudget(toCopy.budget); setTags(toCopy.tags); } @@ -157,7 +158,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, phone, email, budget, tags); } public void setName(Name name) { @@ -184,12 +185,12 @@ public Optional getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; + public void setBudget(Budget budget) { + this.budget = budget; } - public Optional
getAddress() { - return Optional.ofNullable(address); + public Optional getBudget() { + return Optional.ofNullable(budget); } /** @@ -216,16 +217,16 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { + if (!(other instanceof EditCustomerDescriptor)) { return false; } - EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other; - return Objects.equals(name, otherEditPersonDescriptor.name) - && Objects.equals(phone, otherEditPersonDescriptor.phone) - && Objects.equals(email, otherEditPersonDescriptor.email) - && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); + EditCustomerDescriptor otherEditCustomerDescriptor = (EditCustomerDescriptor) other; + return Objects.equals(name, otherEditCustomerDescriptor.name) + && Objects.equals(phone, otherEditCustomerDescriptor.phone) + && Objects.equals(email, otherEditCustomerDescriptor.email) + && Objects.equals(budget, otherEditCustomerDescriptor.budget) + && Objects.equals(tags, otherEditCustomerDescriptor.tags); } @Override @@ -234,7 +235,7 @@ public String toString() { .add("name", name) .add("phone", phone) .add("email", email) - .add("address", address) + .add("budget", budget) .add("tags", tags) .toString(); } diff --git a/src/main/java/seedu/address/logic/commands/EditPropertyCommand.java b/src/main/java/seedu/address/logic/commands/EditPropertyCommand.java new file mode 100644 index 00000000000..cb685ab1868 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditPropertyCommand.java @@ -0,0 +1,243 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PROPERTIES; + +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 seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +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.property.Price; +import seedu.address.model.property.PropAddress; +import seedu.address.model.property.PropName; +import seedu.address.model.property.PropPhone; +import seedu.address.model.property.Property; +import seedu.address.model.tag.Tag; + +/** + * Edits the details of an existing property in the Price book. + */ +public class EditPropertyCommand extends Command { + + public static final String COMMAND_WORD = "editprop"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the property identified " + + "by the index number used in the displayed property list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_PRICE + "PRICE] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PHONE + "91234567 " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25"; + + public static final String MESSAGE_EDIT_PROPERTY_SUCCESS = "Edited Property: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_PROPERTY = "This property has a duplicate address."; + + private final Index index; + private final EditPropertyDescriptor editPropertyDescriptor; + + /** + * @param index of the property in the filtered property list to edit + * @param editPropertyDescriptor details to edit the property with + */ + public EditPropertyCommand(Index index, EditPropertyDescriptor editPropertyDescriptor) { + requireNonNull(index); + requireNonNull(editPropertyDescriptor); + + this.index = index; + this.editPropertyDescriptor = new EditPropertyDescriptor(editPropertyDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPropertyList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PROPERTY_DISPLAYED_INDEX); + } + + Property propertyToEdit = lastShownList.get(index.getZeroBased()); + Property editedproperty = createEditedproperty(propertyToEdit, editPropertyDescriptor); + + if (!propertyToEdit.isSameProperty(editedproperty) && model.hasProperty(editedproperty)) { + throw new CommandException(MESSAGE_DUPLICATE_PROPERTY); + } + + model.setProperty(propertyToEdit, editedproperty); + model.updateFilteredPropertyList(PREDICATE_SHOW_ALL_PROPERTIES); + return new CommandResult(String.format(MESSAGE_EDIT_PROPERTY_SUCCESS, Messages.format(editedproperty))); + } + + /** + * Creates and returns a {@code property} with the details of {@code propertyToEdit} + * edited with {@code editpropertyDescriptor}. + */ + private static Property createEditedproperty(Property propertyToEdit, + EditPropertyDescriptor editpropertyDescriptor) { + assert propertyToEdit != null; + + PropName updatedName = editpropertyDescriptor.getName().orElse(propertyToEdit.getName()); + PropPhone updatedPhone = editpropertyDescriptor.getPhone().orElse(propertyToEdit.getPhone()); + PropAddress updatedAddress = editpropertyDescriptor.getAddress().orElse(propertyToEdit.getAddress()); + Price updatedPrice = editpropertyDescriptor.getPrice().orElse(propertyToEdit.getPrice()); + Set updatedTags = editpropertyDescriptor.getTags().orElse(propertyToEdit.getTags()); + + return new Property(updatedName, updatedAddress, updatedPhone, updatedPrice, updatedTags); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPropertyCommand)) { + return false; + } + + EditPropertyCommand otherEditCommand = (EditPropertyCommand) other; + return index.equals(otherEditCommand.index) + && editPropertyDescriptor.equals(otherEditCommand.editPropertyDescriptor); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", index) + .add("editPropertyDescriptor", editPropertyDescriptor) + .toString(); + } + + /** + * Stores the details to edit the property with. Each non-empty field value will replace the + * corresponding field value of the property. + */ + public static class EditPropertyDescriptor { + private PropName name; + private PropPhone phone; + private PropAddress address; + private Price price; + private Set tags; + + public EditPropertyDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditPropertyDescriptor(EditPropertyDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setAddress(toCopy.address); + setPrice(toCopy.price); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, phone, address, price, tags); + } + + public void setName(PropName name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(PropPhone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setAddress(PropAddress address) { + this.address = address; + } + + public Optional getAddress() { + return Optional.ofNullable(address); + } + + public void setPrice(Price price) { + this.price = price; + } + + public Optional getPrice() { + return Optional.ofNullable(price); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPropertyDescriptor)) { + return false; + } + + EditPropertyDescriptor otherEditpropertyDescriptor = (EditPropertyDescriptor) other; + return Objects.equals(name, otherEditpropertyDescriptor.name) + && Objects.equals(phone, otherEditpropertyDescriptor.phone) + && Objects.equals(address, otherEditpropertyDescriptor.address) + && Objects.equals(price, otherEditpropertyDescriptor.price) + && Objects.equals(tags, otherEditpropertyDescriptor.tags); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("name", name) + .add("phone", phone) + .add("address", address) + .add("price", price) + .add("tags", tags) + .toString(); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..de12aa0aad3 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -9,7 +9,7 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Goodbye! Exiting PropertyMatch as requested..."; @Override public CommandResult execute(Model model) { diff --git a/src/main/java/seedu/address/logic/commands/FilterCustomerCommand.java b/src/main/java/seedu/address/logic/commands/FilterCustomerCommand.java new file mode 100644 index 00000000000..23fb6416221 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterCustomerCommand.java @@ -0,0 +1,153 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BUDGET; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.util.CollectionUtil; +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.customer.Budget; +import seedu.address.model.customer.BudgetAndTagsInRangePredicate; +import seedu.address.model.tag.Tag; + +/** + * Filter all customers in the address book to the user based on specific tags and/or budget. + */ +public class FilterCustomerCommand extends Command { + public static final String COMMAND_WORD = "filtercust"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters customers from the address book. " + + "Parameters: " + + "[" + PREFIX_BUDGET + "BUDGET] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_BUDGET + "100000000 " + + PREFIX_TAG + "bright " + + PREFIX_TAG + "sunny"; + + private BudgetAndTagsInRangePredicate predicate; + + /** + * Creates an FilteredCustomerCommand to get all the specified {@code Customer} + */ + public FilterCustomerCommand(BudgetAndTagsInRangePredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredCustomerList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_CUSTOMERS_LISTED_OVERVIEW, model.getFilteredCustomerList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilterCustomerCommand)) { + return false; + } + + FilterCustomerCommand otherFilterCustomerCommand = (FilterCustomerCommand) other; + return predicate.equals(otherFilterCustomerCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } + + /** + * Stores the details to filter the customer with budget and tags. + */ + public static class FilterCustomerDescriptor { + private Budget budget; + private Set tags; + + public FilterCustomerDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public FilterCustomerDescriptor(FilterCustomerDescriptor toCopy) { + setBudget(toCopy.budget); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldFiltered() { + return CollectionUtil.isAnyNonNull(budget, tags); + } + + public void setBudget(Budget budget) { + this.budget = budget; + } + + public Optional getBudget() { + return Optional.ofNullable(budget); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + public BudgetAndTagsInRangePredicate getPredicate() { + return new BudgetAndTagsInRangePredicate(budget, tags); + } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilterCustomerDescriptor)) { + return false; + } + + FilterCustomerDescriptor otherFilterPropertyDescriptor = (FilterCustomerDescriptor) other; + return Objects.equals(budget, otherFilterPropertyDescriptor.budget) + && Objects.equals(tags, otherFilterPropertyDescriptor.tags); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("budget", budget) + .add("tags", tags) + .toString(); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/FilterPropertyCommand.java b/src/main/java/seedu/address/logic/commands/FilterPropertyCommand.java new file mode 100644 index 00000000000..4922c78a02f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterPropertyCommand.java @@ -0,0 +1,153 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.util.CollectionUtil; +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.property.Price; +import seedu.address.model.property.PriceAndTagsInRangePredicate; +import seedu.address.model.tag.Tag; + +/** + * Filter all properties in the address book to the user based on specific tags and/or price. + */ +public class FilterPropertyCommand extends Command { + public static final String COMMAND_WORD = "filterprop"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters properties from the address book. " + + "Parameters: " + + "[" + PREFIX_PRICE + "PRICE] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_PRICE + "100000000 " + + PREFIX_TAG + "bright " + + PREFIX_TAG + "sunny"; + + private PriceAndTagsInRangePredicate predicate; + + /** + * Creates an FilteredPropertyCommand to get all the specified {@code Property} + */ + public FilterPropertyCommand(PriceAndTagsInRangePredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredPropertyList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PROPERTIES_LISTED_OVERVIEW, model.getFilteredPropertyList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilterPropertyCommand)) { + return false; + } + + FilterPropertyCommand otherFilterPropertyCommand = (FilterPropertyCommand) other; + return predicate.equals(otherFilterPropertyCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } + + /** + * Stores the details to filter the property with price and tags. + */ + public static class FilterPropertyDescriptor { + private Price price; + private Set tags; + + public FilterPropertyDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public FilterPropertyDescriptor(FilterPropertyDescriptor toCopy) { + setPrice(toCopy.price); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldFiltered() { + return CollectionUtil.isAnyNonNull(price, tags); + } + + public void setPrice(Price price) { + this.price = price; + } + + public Optional getPrice() { + return Optional.ofNullable(price); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + public PriceAndTagsInRangePredicate getPredicate() { + return new PriceAndTagsInRangePredicate(price, tags); + } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilterPropertyDescriptor)) { + return false; + } + + FilterPropertyDescriptor otherFilterPropertyDescriptor = (FilterPropertyDescriptor) other; + return Objects.equals(price, otherFilterPropertyDescriptor.price) + && Objects.equals(tags, otherFilterPropertyDescriptor.tags); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("price", price) + .add("tags", tags) + .toString(); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCustomerCommand.java similarity index 59% rename from src/main/java/seedu/address/logic/commands/FindCommand.java rename to src/main/java/seedu/address/logic/commands/FindCustomerCommand.java index 72b9eddd3a7..fd8562af632 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCustomerCommand.java @@ -5,33 +5,33 @@ 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.customer.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all customers in address book whose name contains any of the argument keywords. * Keyword matching is case insensitive. */ -public class FindCommand extends Command { +public class FindCustomerCommand extends Command { - public static final String COMMAND_WORD = "find"; + public static final String COMMAND_WORD = "findcust"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all customers whose names contain any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + "Example: " + COMMAND_WORD + " alice bob charlie"; private final NameContainsKeywordsPredicate predicate; - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindCustomerCommand(NameContainsKeywordsPredicate predicate) { this.predicate = predicate; } @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(predicate); + model.updateFilteredCustomerList(predicate); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + String.format(Messages.MESSAGE_CUSTOMERS_LISTED_OVERVIEW, model.getFilteredCustomerList().size())); } @Override @@ -41,12 +41,12 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof FindCommand)) { + if (!(other instanceof FindCustomerCommand)) { return false; } - FindCommand otherFindCommand = (FindCommand) other; - return predicate.equals(otherFindCommand.predicate); + FindCustomerCommand otherFindCustomerCommand = (FindCustomerCommand) other; + return predicate.equals(otherFindCustomerCommand.predicate); } @Override diff --git a/src/main/java/seedu/address/logic/commands/FindPropertyCommand.java b/src/main/java/seedu/address/logic/commands/FindPropertyCommand.java new file mode 100644 index 00000000000..67324a915ad --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindPropertyCommand.java @@ -0,0 +1,59 @@ +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.property.PropNameContainsKeywordsPredicate; + + +/** + * Finds and lists all properties in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindPropertyCommand extends Command { + + public static final String COMMAND_WORD = "findprop"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all properties 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 + " Aquavista Luxeloft"; + + private final PropNameContainsKeywordsPredicate predicate; + + public FindPropertyCommand(PropNameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPropertyList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PROPERTIES_LISTED_OVERVIEW, model.getFilteredPropertyList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindPropertyCommand)) { + return false; + } + + FindPropertyCommand otherFindCommand = (FindPropertyCommand) other; + return predicate.equals(otherFindCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCustomerCommand.java b/src/main/java/seedu/address/logic/commands/ListCustomerCommand.java new file mode 100644 index 00000000000..121cd2911ed --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListCustomerCommand.java @@ -0,0 +1,24 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CUSTOMERS; + +import seedu.address.model.Model; + +/** + * Lists all customers in the address book to the user. + */ +public class ListCustomerCommand extends Command { + + public static final String COMMAND_WORD = "listcust"; + + public static final String MESSAGE_SUCCESS = "Listed all customers"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredCustomerList(PREDICATE_SHOW_ALL_CUSTOMERS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListPropertyCommand.java b/src/main/java/seedu/address/logic/commands/ListPropertyCommand.java new file mode 100644 index 00000000000..863c96e585c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListPropertyCommand.java @@ -0,0 +1,24 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PROPERTIES; + +import seedu.address.model.Model; + +/** + * Lists all customers in the address book to the user. + */ +public class ListPropertyCommand extends Command { + + public static final String COMMAND_WORD = "listprop"; + + public static final String MESSAGE_SUCCESS = "Listed all properties"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPropertyList(PREDICATE_SHOW_ALL_PROPERTIES); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/MatchCustomerCommand.java b/src/main/java/seedu/address/logic/commands/MatchCustomerCommand.java new file mode 100644 index 00000000000..dcb36483331 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MatchCustomerCommand.java @@ -0,0 +1,88 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Set; + +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.customer.Budget; +import seedu.address.model.customer.Customer; +import seedu.address.model.property.Price; +import seedu.address.model.property.PriceAndOneTagsPredicate; +import seedu.address.model.tag.Tag; + +/** + * Match all properties in the property book to the user based on specific tags + * and/or budget satisfy the customer criteria. + */ +public class MatchCustomerCommand extends Command { + public static final String COMMAND_WORD = "matchcust"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Match customers from the address book. \n" + + "Parameters: " + + "Index" + "\n" + + "Example: " + COMMAND_WORD + " 2"; + + public static final String MESSAGE_FAIL = "There is no customer with index "; + private Index targetIndex; + + /** + * Creates a MatchCustomerCommand to get all the specified {@code Property} + */ + public MatchCustomerCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredCustomerList(); + + try { + Customer targetCustomer = lastShownList.get(targetIndex.getZeroBased()); + Budget budget = targetCustomer.getBudget(); + Set tags = targetCustomer.getTags(); + + Price maxPrice = budget.convertToPrice(); + PriceAndOneTagsPredicate predicate = new PriceAndOneTagsPredicate(maxPrice, tags); + + model.updateMatchedCustomerList(targetCustomer, predicate); + + return new CommandResult( + String.format( + Messages.MESSAGE_CUSTOMERS_MATCH_OVERVIEW + targetIndex.getOneBased(), + model.getFilteredPropertyList().size() + ) + ); + } catch (IndexOutOfBoundsException e) { + throw new CommandException(MESSAGE_FAIL + targetIndex.getOneBased()); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof MatchCustomerCommand)) { + return false; + } + + MatchCustomerCommand otherMatchCustomerCommand = (MatchCustomerCommand) other; + return targetIndex.equals(otherMatchCustomerCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("Index", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/MatchPropertyCommand.java b/src/main/java/seedu/address/logic/commands/MatchPropertyCommand.java new file mode 100644 index 00000000000..ba7294ef430 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MatchPropertyCommand.java @@ -0,0 +1,89 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Set; + +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.customer.Budget; +import seedu.address.model.customer.BudgetAndOneTagsPredicate; +import seedu.address.model.property.Price; +import seedu.address.model.property.Property; +import seedu.address.model.tag.Tag; + +/** + * Match all properties in the property book to the user based on specific tags + * and/or budget satisfy the customer criteria. + */ +public class MatchPropertyCommand extends Command { + public static final String COMMAND_WORD = "matchprop"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Match properties from the property book. \n" + + "Parameters: " + + "Index" + "\n" + + "Example: " + COMMAND_WORD + " 2"; + + public static final String MESSAGE_FAIL = "There is no property with index "; + private Index targetIndex; + + /** + * Creates a MatchPropertyCommand to get all the specified {@code Customer} + */ + public MatchPropertyCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPropertyList(); + + try { + Property targetProperty = lastShownList.get(targetIndex.getZeroBased()); + + Price price = targetProperty.getPrice(); + Set tags = targetProperty.getTags(); + + Budget minBudget = price.convertToBudget(); + BudgetAndOneTagsPredicate predicate = new BudgetAndOneTagsPredicate(minBudget, tags); + + model.updateMatchedPropertyList(targetProperty, predicate); + + return new CommandResult( + String.format( + Messages.MESSAGE_PROPERTIES_MATCH_OVERVIEW + targetIndex.getOneBased(), + model.getFilteredCustomerList().size() + ) + ); + } catch (IndexOutOfBoundsException e) { + throw new CommandException(MESSAGE_FAIL + targetIndex.getOneBased()); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof MatchPropertyCommand)) { + return false; + } + + MatchPropertyCommand otherMatchPropertyCommand = (MatchPropertyCommand) other; + return targetIndex.equals(otherMatchPropertyCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("Index", targetIndex) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCustomerCommandParser.java similarity index 62% rename from src/main/java/seedu/address/logic/parser/AddCommandParser.java rename to src/main/java/seedu/address/logic/parser/AddCustomerCommandParser.java index 4ff1a97ed77..b170223e9d8 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCustomerCommandParser.java @@ -1,7 +1,7 @@ package seedu.address.logic.parser; 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_BUDGET; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; @@ -10,44 +10,44 @@ import java.util.Set; import java.util.stream.Stream; -import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddCustomerCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.customer.Budget; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; import seedu.address.model.tag.Tag; /** - * Parses input arguments and creates a new AddCommand object + * Parses input arguments and creates a new AddCustomerCommand object */ -public class AddCommandParser implements Parser { +public class AddCustomerCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. + * Parses the given {@code String} of arguments in the context of the AddCustomerCommand + * and returns an AddCustomerCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ - public AddCommand parse(String args) throws ParseException { + public AddCustomerCommand 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_BUDGET, PREFIX_TAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_BUDGET, PREFIX_PHONE, PREFIX_EMAIL) || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCustomerCommand.MESSAGE_USAGE)); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_BUDGET); 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()); + Budget budget = ParserUtil.parseBudget(argMultimap.getValue(PREFIX_BUDGET).get()); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); + Customer customer = new Customer(name, phone, email, budget, tagList); - return new AddCommand(person); + return new AddCustomerCommand(customer); } /** diff --git a/src/main/java/seedu/address/logic/parser/AddPropertyCommandParser.java b/src/main/java/seedu/address/logic/parser/AddPropertyCommandParser.java new file mode 100644 index 00000000000..83d3cc06767 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddPropertyCommandParser.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser; + +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_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddPropertyCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.property.Price; +import seedu.address.model.property.PropAddress; +import seedu.address.model.property.PropName; +import seedu.address.model.property.PropPhone; +import seedu.address.model.property.Property; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddPropertyCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddPropertyCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_PRICE, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_PRICE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPropertyCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_PRICE); + PropName propName = ParserUtil.parsePropName(argMultimap.getValue(PREFIX_NAME).get()); + PropAddress propAddress = ParserUtil.parsePropAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + PropPhone propPhone = ParserUtil.parsePropPhone(argMultimap.getValue(PREFIX_PHONE).get()); + Price price = ParserUtil.parsePrice(argMultimap.getValue(PREFIX_PRICE).get()); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + Property property = new Property(propName, propAddress, propPhone, price, tagList); + + return new AddPropertyCommand(property); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 3149ee07e0b..1eecb64b10f 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -8,15 +8,24 @@ import java.util.regex.Pattern; import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddCustomerCommand; +import seedu.address.logic.commands.AddPropertyCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.DeleteCustomerCommand; +import seedu.address.logic.commands.DeletePropertyCommand; +import seedu.address.logic.commands.EditCustomerCommand; +import seedu.address.logic.commands.EditPropertyCommand; import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FilterCustomerCommand; +import seedu.address.logic.commands.FilterPropertyCommand; +import seedu.address.logic.commands.FindCustomerCommand; +import seedu.address.logic.commands.FindPropertyCommand; import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ListCustomerCommand; +import seedu.address.logic.commands.ListPropertyCommand; +import seedu.address.logic.commands.MatchCustomerCommand; +import seedu.address.logic.commands.MatchPropertyCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -53,23 +62,50 @@ public Command parseCommand(String userInput) throws ParseException { switch (commandWord) { - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); + case AddCustomerCommand.COMMAND_WORD: + return new AddCustomerCommandParser().parse(arguments); - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); + case AddPropertyCommand.COMMAND_WORD: + return new AddPropertyCommandParser().parse(arguments); - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); + case EditCustomerCommand.COMMAND_WORD: + return new EditCustomerCommandParser().parse(arguments); + + case EditPropertyCommand.COMMAND_WORD: + return new EditPropertyCommandParser().parse(arguments); + + case DeletePropertyCommand.COMMAND_WORD: + return new DeletePropertyCommandParser().parse(arguments); + + case DeleteCustomerCommand.COMMAND_WORD: + return new DeleteCustomerCommandParser().parse(arguments); case ClearCommand.COMMAND_WORD: return new ClearCommand(); - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); + case FindCustomerCommand.COMMAND_WORD: + return new FindCustomerCommandParser().parse(arguments); + + case FindPropertyCommand.COMMAND_WORD: + return new FindPropertyCommandParser().parse(arguments); + + case FilterCustomerCommand.COMMAND_WORD: + return new FilterCustomerCommandParser().parse(arguments); + + case FilterPropertyCommand.COMMAND_WORD: + return new FilterPropertyCommandParser().parse(arguments); + + case ListCustomerCommand.COMMAND_WORD: + return new ListCustomerCommand(); + + case ListPropertyCommand.COMMAND_WORD: + return new ListPropertyCommand(); + + case MatchCustomerCommand.COMMAND_WORD: + return new MatchCustomerCommandParser().parse(arguments); - case ListCommand.COMMAND_WORD: - return new ListCommand(); + case MatchPropertyCommand.COMMAND_WORD: + return new MatchPropertyCommandParser().parse(arguments); case ExitCommand.COMMAND_WORD: return new ExitCommand(); diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..11ffac66b4b 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -9,7 +9,9 @@ public class CliSyntax { public static final Prefix PREFIX_NAME = new Prefix("n/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); + public static final Prefix PREFIX_PRICE = new Prefix("pr/"); + public static final Prefix PREFIX_BUDGET = new Prefix("b/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_TAG = new Prefix("c/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCustomerCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCustomerCommandParser.java new file mode 100644 index 00000000000..584a3c127b0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteCustomerCommandParser.java @@ -0,0 +1,29 @@ +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.DeleteCustomerCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteCustomerCommand object + */ +public class DeleteCustomerCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCustomerCommand + * and returns a DeleteCustomerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteCustomerCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteCustomerCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCustomerCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeletePropertyCommandParser.java similarity index 58% rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java rename to src/main/java/seedu/address/logic/parser/DeletePropertyCommandParser.java index 3527fe76a3e..5418af72274 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeletePropertyCommandParser.java @@ -3,26 +3,26 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeletePropertyCommand; import seedu.address.logic.parser.exceptions.ParseException; /** - * Parses input arguments and creates a new DeleteCommand object + * Parses input arguments and creates a new DeletePropertyCommand object */ -public class DeleteCommandParser implements Parser { +public class DeletePropertyCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. + * and returns a DeletePropertyCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ - public DeleteCommand parse(String args) throws ParseException { + public DeletePropertyCommand parse(String args) throws ParseException { try { Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + return new DeletePropertyCommand(index); } catch (ParseException pe) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeletePropertyCommand.MESSAGE_USAGE), pe); } } diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCustomerCommandParser.java similarity index 63% rename from src/main/java/seedu/address/logic/parser/EditCommandParser.java rename to src/main/java/seedu/address/logic/parser/EditCustomerCommandParser.java index 46b3309a78b..f5e1634a490 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCustomerCommandParser.java @@ -2,7 +2,7 @@ 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_BUDGET; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; @@ -14,57 +14,58 @@ import java.util.Set; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.commands.EditCustomerCommand; +import seedu.address.logic.commands.EditCustomerCommand.EditCustomerDescriptor; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.tag.Tag; /** * Parses input arguments and creates a new EditCommand object */ -public class EditCommandParser implements Parser { +public class EditCustomerCommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the EditCommand * and returns an EditCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ - public EditCommand parse(String args) throws ParseException { + public EditCustomerCommand 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_BUDGET, PREFIX_TAG); Index index; try { index = ParserUtil.parseIndex(argMultimap.getPreamble()); } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCustomerCommand.MESSAGE_USAGE), + pe); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_BUDGET); - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + EditCustomerDescriptor editCustomerDescriptor = new EditCustomerDescriptor(); if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + editCustomerDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); } if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + editCustomerDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); } if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + editCustomerDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + if (argMultimap.getValue(PREFIX_BUDGET).isPresent()) { + editCustomerDescriptor.setBudget(ParserUtil.parseBudget(argMultimap.getValue(PREFIX_BUDGET).get())); } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editCustomerDescriptor::setTags); - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + if (!editCustomerDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditCustomerCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); + return new EditCustomerCommand(index, editCustomerDescriptor); } /** diff --git a/src/main/java/seedu/address/logic/parser/EditPropertyCommandParser.java b/src/main/java/seedu/address/logic/parser/EditPropertyCommandParser.java new file mode 100644 index 00000000000..f43bf1a6e4a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditPropertyCommandParser.java @@ -0,0 +1,86 @@ +package seedu.address.logic.parser; + +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_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditPropertyCommand; +import seedu.address.logic.commands.EditPropertyCommand.EditPropertyDescriptor; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new EditCommand object + */ +public class EditPropertyCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditCommand + * and returns an EditCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditPropertyCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, PREFIX_PRICE, PREFIX_TAG); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditPropertyCommand.MESSAGE_USAGE), pe); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_ADDRESS, PREFIX_PRICE); + + EditPropertyDescriptor editPropertyDescriptor = new EditPropertyDescriptor(); + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editPropertyDescriptor.setName(ParserUtil.parsePropName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editPropertyDescriptor.setPhone(ParserUtil.parsePropPhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + editPropertyDescriptor.setAddress(ParserUtil.parsePropAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + } + if (argMultimap.getValue(PREFIX_PRICE).isPresent()) { + editPropertyDescriptor.setPrice(ParserUtil.parsePrice(argMultimap.getValue(PREFIX_PRICE).get())); + } + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPropertyDescriptor::setTags); + + if (!editPropertyDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditPropertyCommand.MESSAGE_NOT_EDITED); + } + + return new EditPropertyCommand(index, editPropertyDescriptor); + } + + /** + * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. + * If {@code tags} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero tags. + */ + private Optional> parseTagsForEdit(Collection tags) throws ParseException { + assert tags != null; + + if (tags.isEmpty()) { + return Optional.empty(); + } + Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; + return Optional.of(ParserUtil.parseTags(tagSet)); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FilterCustomerCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCustomerCommandParser.java new file mode 100644 index 00000000000..7162c56bd38 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterCustomerCommandParser.java @@ -0,0 +1,45 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BUDGET; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.address.logic.commands.FilterCustomerCommand; +import seedu.address.logic.commands.FilterCustomerCommand.FilterCustomerDescriptor; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Filters and lists all customers in address book whose budget and/or tags are selected. + */ +public class FilterCustomerCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterCustomerCommand + * and returns a FilterPropertyCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterCustomerCommand parse(String args) throws ParseException { + requireNonNull(args); + FilterCustomerDescriptor descriptor = new FilterCustomerDescriptor(); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_BUDGET, PREFIX_TAG); + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_BUDGET); + if (argMultimap.getValue(PREFIX_BUDGET).isPresent()) { + descriptor.setBudget(ParserUtil.parseBudget(argMultimap.getValue(PREFIX_BUDGET).get())); + } + + if (argMultimap.getValue(PREFIX_TAG).isPresent()) { + descriptor.setTags(ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG))); + } + + if (!descriptor.isAnyFieldFiltered()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FilterCustomerCommand.MESSAGE_USAGE)); + } + + return new FilterCustomerCommand(descriptor.getPredicate()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FilterPropertyCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterPropertyCommandParser.java new file mode 100644 index 00000000000..8d7218aadbe --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterPropertyCommandParser.java @@ -0,0 +1,43 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.address.logic.commands.FilterPropertyCommand; +import seedu.address.logic.commands.FilterPropertyCommand.FilterPropertyDescriptor; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Filters and lists all properties in address book whose price and/or tags are selected. + */ +public class FilterPropertyCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the FilterPropertyCommand + * and returns a FilterPropertyCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FilterPropertyCommand parse(String args) throws ParseException { + requireNonNull(args); + FilterPropertyDescriptor descriptor = new FilterPropertyDescriptor(); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_PRICE, PREFIX_TAG); + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_PRICE); + if (argMultimap.getValue(PREFIX_PRICE).isPresent()) { + descriptor.setPrice(ParserUtil.parsePrice(argMultimap.getValue(PREFIX_PRICE).get())); + } + + if (argMultimap.getValue(PREFIX_TAG).isPresent()) { + descriptor.setTags(ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG))); + } + + if (!descriptor.isAnyFieldFiltered()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FilterPropertyCommand.MESSAGE_USAGE)); + } + + return new FilterPropertyCommand(descriptor.getPredicate()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCustomerCommandParser.java similarity index 64% rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java rename to src/main/java/seedu/address/logic/parser/FindCustomerCommandParser.java index 2867bde857b..1431f022a0f 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCustomerCommandParser.java @@ -4,30 +4,30 @@ import java.util.Arrays; -import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindCustomerCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.customer.NameContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object */ -public class FindCommandParser implements Parser { +public class FindCustomerCommandParser implements Parser { /** * 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 { + public FindCustomerCommand parse(String args) throws ParseException { String trimmedArgs = args.trim(); if (trimmedArgs.isEmpty()) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCustomerCommand.MESSAGE_USAGE)); } String[] nameKeywords = trimmedArgs.split("\\s+"); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + return new FindCustomerCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); } } diff --git a/src/main/java/seedu/address/logic/parser/FindPropertyCommandParser.java b/src/main/java/seedu/address/logic/parser/FindPropertyCommandParser.java new file mode 100644 index 00000000000..dec19dc60da --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindPropertyCommandParser.java @@ -0,0 +1,33 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FindPropertyCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.property.PropNameContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindPropertyCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindPropertyCommand + * and returns a FindPropertyCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindPropertyCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPropertyCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new FindPropertyCommand(new PropNameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/MatchCustomerCommandParser.java b/src/main/java/seedu/address/logic/parser/MatchCustomerCommandParser.java new file mode 100644 index 00000000000..729f5ab444f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/MatchCustomerCommandParser.java @@ -0,0 +1,37 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.MatchCustomerCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Filters and lists all customers in address book whose budget and/or tags are selected. + */ +public class MatchCustomerCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FilterCommand + * and returns a FilterCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MatchCustomerCommand parse(String args) throws ParseException { + requireNonNull(args); + Index index; + + try { + index = ParserUtil.parseIndex(args); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + MatchCustomerCommand.MESSAGE_USAGE), + pe + ); + } + + return new MatchCustomerCommand(index); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/MatchPropertyCommandParser.java b/src/main/java/seedu/address/logic/parser/MatchPropertyCommandParser.java new file mode 100644 index 00000000000..a270a32d584 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/MatchPropertyCommandParser.java @@ -0,0 +1,37 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.MatchPropertyCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Match and lists all customers in address book whose budget and/or tags satisfy with the property. + */ +public class MatchPropertyCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the MatchPropertyCommand + * and returns a MatchPropertyCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MatchPropertyCommand parse(String args) throws ParseException { + requireNonNull(args); + Index index; + + try { + index = ParserUtil.parseIndex(args); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + MatchPropertyCommand.MESSAGE_USAGE), + pe + ); + } + + return new MatchPropertyCommand(index); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..d107e28b629 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -9,12 +9,17 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.address.model.customer.Budget; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; +import seedu.address.model.property.Price; +import seedu.address.model.property.PropAddress; +import seedu.address.model.property.PropName; +import seedu.address.model.property.PropPhone; import seedu.address.model.tag.Tag; + /** * Contains utility methods used for parsing strings in the various *Parser classes. */ @@ -65,34 +70,96 @@ public static Phone parsePhone(String phone) throws ParseException { return new Phone(trimmedPhone); } + /** + * Parses a {@code String email} into an {@code Email}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code email} is invalid. + */ + public static Email parseEmail(String email) throws ParseException { + requireNonNull(email); + String trimmedEmail = email.trim(); + if (!Email.isValidEmail(trimmedEmail)) { + throw new ParseException(Email.MESSAGE_CONSTRAINTS); + } + return new Email(trimmedEmail); + } + + /** + * Parses a {@code String name} into a {@code Name}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code name} is invalid. + */ + public static PropName parsePropName(String name) throws ParseException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!PropName.isValidName(trimmedName)) { + throw new ParseException(PropName.MESSAGE_CONSTRAINTS); + } + return new PropName(trimmedName); + } + + /** + * Parses a {@code String phone} into a {@code Phone}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code phone} is invalid. + */ + public static PropPhone parsePropPhone(String phone) throws ParseException { + requireNonNull(phone); + String trimmedPhone = phone.trim(); + if (!PropPhone.isValidPhone(trimmedPhone)) { + throw new ParseException(PropPhone.MESSAGE_CONSTRAINTS); + } + return new PropPhone(trimmedPhone); + } + /** * Parses a {@code String address} into an {@code Address}. * Leading and trailing whitespaces will be trimmed. * * @throws ParseException if the given {@code address} is invalid. */ - public static Address parseAddress(String address) throws ParseException { + public static PropAddress parsePropAddress(String address) throws ParseException { requireNonNull(address); String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); + if (!PropAddress.isValidAddress(trimmedAddress)) { + throw new ParseException(PropAddress.MESSAGE_CONSTRAINTS); } - return new Address(trimmedAddress); + return new PropAddress(trimmedAddress); } /** - * Parses a {@code String email} into an {@code Email}. + * Parses a {@code String price} into an {@code Price}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code email} is invalid. + * @throws ParseException if the given {@code price} is invalid. */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); + public static Price parsePrice(String price) throws ParseException { + requireNonNull(price); + String trimmedPrice = price.trim(); + if (!Price.isValidPrice(trimmedPrice)) { + throw new ParseException(Price.MESSAGE_CONSTRAINTS); } - return new Email(trimmedEmail); + return new Price(trimmedPrice); + } + + /** + * Parses a {@code String budget} into an {@code Budget} + * Loading and trailing whitespaces will be trimmed + * + * @param budget the budget of the customer in string + * @return the budget of the customer in Budget + * @throws ParseException if the given {@code budget} is invalid + */ + public static Budget parseBudget(String budget) throws ParseException { + requireNonNull(budget); + String trimmedBudged = budget.trim(); + if (!Budget.isValidBudget(trimmedBudged)) { + throw new ParseException(Budget.MESSAGE_CONSTRAINTS); + } + return new Budget(trimmedBudged); } /** diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 73397161e84..bed9ad65816 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -6,16 +6,16 @@ import javafx.collections.ObservableList; import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.UniqueCustomerList; /** * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) + * Duplicates are not allowed (by .isSameCustomer comparison) */ public class AddressBook implements ReadOnlyAddressBook { - private final UniquePersonList persons; + private final UniqueCustomerList customers; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -25,13 +25,13 @@ public class AddressBook implements ReadOnlyAddressBook { * among constructors. */ { - persons = new UniquePersonList(); + customers = new UniqueCustomerList(); } public AddressBook() {} /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} + * Creates an AddressBook using the Customers in the {@code toBeCopied} */ public AddressBook(ReadOnlyAddressBook toBeCopied) { this(); @@ -41,11 +41,11 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { //// list overwrite operations /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. + * Replaces the contents of the customer list with {@code customers}. + * {@code customers} must not contain duplicate customers. */ - public void setPersons(List persons) { - this.persons.setPersons(persons); + public void setCustomers(List customers) { + this.customers.setCustomers(customers); } /** @@ -54,44 +54,45 @@ public void setPersons(List persons) { public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); - setPersons(newData.getPersonList()); + setCustomers(newData.getCustomerList()); } - //// person-level operations + //// customer-level operations /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a customer with the same identity as {@code customer} exists in the address book. */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); + public boolean hasCustomer(Customer customer) { + requireNonNull(customer); + return customers.contains(customer); } /** - * Adds a person to the address book. - * The person must not already exist in the address book. + * Adds a customer to the address book. + * The customer must not already exist in the address book. */ - public void addPerson(Person p) { - persons.add(p); + public void addCustomer(Customer p) { + customers.add(p); } /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. + * Replaces the given customer {@code target} in the list with {@code editedCustomer}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The customer identity of {@code editedCustomer} must not be the same as + * another existing customer in the address book. */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); + public void setCustomer(Customer target, Customer editedCustomer) { + requireNonNull(editedCustomer); - persons.setPerson(target, editedPerson); + customers.setCustomer(target, editedCustomer); } /** * Removes {@code key} from this {@code AddressBook}. * {@code key} must exist in the address book. */ - public void removePerson(Person key) { - persons.remove(key); + public void removeCustomer(Customer key) { + customers.remove(key); } //// util methods @@ -99,13 +100,13 @@ public void removePerson(Person key) { @Override public String toString() { return new ToStringBuilder(this) - .add("persons", persons) + .add("customers", customers) .toString(); } @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); + public ObservableList getCustomerList() { + return customers.asUnmodifiableObservableList(); } @Override @@ -120,11 +121,11 @@ public boolean equals(Object other) { } AddressBook otherAddressBook = (AddressBook) other; - return persons.equals(otherAddressBook.persons); + return customers.equals(otherAddressBook.customers); } @Override public int hashCode() { - return persons.hashCode(); + return customers.hashCode(); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..f359e4f3d63 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -5,14 +5,18 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; +import seedu.address.model.customer.Customer; +import seedu.address.model.property.Property; /** * The API of the Model component. */ public interface Model { /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_CUSTOMERS = unused -> true; + + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_PROPERTIES = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -39,49 +43,114 @@ public interface Model { */ Path getAddressBookFilePath(); + /** + * Returns the user prefs' property book file path. + */ + Path getPropertyBookFilePath(); + /** * Sets the user prefs' address book file path. */ void setAddressBookFilePath(Path addressBookFilePath); + /** + * Sets the user prefs' address book file path. + */ + void setPropertyBookFilePath(Path propertyBookFilePath); + /** * Replaces address book data with the data in {@code addressBook}. */ void setAddressBook(ReadOnlyAddressBook addressBook); + /** + * Replaces address book data with the data in {@code propertyBook}. + */ + void setPropertyBook(ReadOnlyPropertyBook propertyBook); + /** Returns the AddressBook */ ReadOnlyAddressBook getAddressBook(); + /** Returns the PropertyBook */ + ReadOnlyPropertyBook getPropertyBook(); + + /** + * Returns true if a customer with the same identity as {@code customer} exists in the address book. + */ + boolean hasCustomer(Customer customer); + + /** + * Returns true if a property with the same identity as {@code property} exists in the PropertyMatch. + */ + boolean hasProperty(Property property); + /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Deletes the given customer. + * The customer must exist in the address book. */ - boolean hasPerson(Person person); + void deleteCustomer(Customer target); /** - * Deletes the given person. - * The person must exist in the address book. + * Deletes the given property. + * The property must exist in the PropertyMatch. */ - void deletePerson(Person target); + void deleteProperty(Property property); + + /** + * Adds the given customer. + * {@code customer} must not already exist in the address book. + */ + void addCustomer(Customer customer); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Adds the given property. + * {@code property} must not already exist in the PropertyMatch. */ - void addPerson(Person person); + void addProperty(Property property); /** - * Replaces the given person {@code target} with {@code editedPerson}. + * Replaces the given customer {@code target} with {@code editedCustomer}. * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * The customer identity of {@code editedCustomer} must not be the same as + * another existing customer in the address book. */ - void setPerson(Person target, Person editedPerson); + void setCustomer(Customer target, Customer editedCustomer); + + /** + * Replaces the given person {@code target} with {@code editedProperty}. + * {@code target} must exist in the address book. + * The property identity of {@code editedProperty} must not + * be the same as another existing property in the address book. + */ + void setProperty(Property target, Property editedProperty); + + /** Returns an unmodifiable view of the filtered customer list */ + ObservableList getFilteredCustomerList(); + + /** Returns an unmodifiable view of the filtered property list */ + ObservableList getFilteredPropertyList(); - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + /** + * Updates the filter of the filtered customer list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredCustomerList(Predicate predicate); /** * 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); + void updateFilteredPropertyList(Predicate predicate); + + /** + * Deletes the given customer. + * The customer must exist in the address book. + */ + void updateMatchedCustomerList(Customer targetCustomer, Predicate predicate); + + /** + * Deletes the given property. + * The property must exist in the PropertyMatch. + */ + void updateMatchedPropertyList(Property targetProperty, Predicate predicate); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..e639f649050 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,7 +11,10 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.SameCustomerPredicate; +import seedu.address.model.property.Property; +import seedu.address.model.property.SamePropertyPredicate; /** * Represents the in-memory model of the address book data. @@ -20,24 +23,32 @@ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); private final AddressBook addressBook; + private final PropertyBook propertyBook; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; + private final FilteredList filteredCustomers; + + private final FilteredList filteredProperties; /** * Initializes a ModelManager with the given addressBook and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - requireAllNonNull(addressBook, userPrefs); + public ModelManager(ReadOnlyAddressBook addressBook, + ReadOnlyPropertyBook propertyBook, ReadOnlyUserPrefs userPrefs) { + + requireAllNonNull(addressBook, propertyBook, userPrefs); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + logger.fine("Initializing with address book: " + addressBook + + ", property book:" + propertyBook + " and user prefs " + userPrefs); this.addressBook = new AddressBook(addressBook); + this.propertyBook = new PropertyBook(propertyBook); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredCustomers = new FilteredList<>(this.addressBook.getCustomerList()); + filteredProperties = new FilteredList<>(this.propertyBook.getPropertyList()); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new AddressBook(), new PropertyBook(), new UserPrefs()); } //=========== UserPrefs ================================================================================== @@ -69,12 +80,23 @@ public Path getAddressBookFilePath() { return userPrefs.getAddressBookFilePath(); } + @Override + public Path getPropertyBookFilePath() { + return userPrefs.getPropertyBookFilePath(); + } + @Override public void setAddressBookFilePath(Path addressBookFilePath) { requireNonNull(addressBookFilePath); userPrefs.setAddressBookFilePath(addressBookFilePath); } + @Override + public void setPropertyBookFilePath(Path propertyBookFilePath) { + requireNonNull(propertyBookFilePath); + userPrefs.setAddressBookFilePath(propertyBookFilePath); + } + //=========== AddressBook ================================================================================ @Override @@ -82,50 +104,118 @@ public void setAddressBook(ReadOnlyAddressBook addressBook) { this.addressBook.resetData(addressBook); } + @Override + public void setPropertyBook(ReadOnlyPropertyBook propertyBook) { + this.propertyBook.resetData(propertyBook); + } + @Override public ReadOnlyAddressBook getAddressBook() { return addressBook; } @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); + public ReadOnlyPropertyBook getPropertyBook() { + return propertyBook; } @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); + public boolean hasCustomer(Customer customer) { + requireNonNull(customer); + return addressBook.hasCustomer(customer); } @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public boolean hasProperty(Property property) { + requireNonNull(property); + return propertyBook.hasProperty(property); } @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public void deleteCustomer(Customer target) { + addressBook.removeCustomer(target); + } - addressBook.setPerson(target, editedPerson); + + @Override + public void deleteProperty(Property target) { + propertyBook.removeProperty(target); + } + + @Override + public void addCustomer(Customer customer) { + addressBook.addCustomer(customer); + updateFilteredCustomerList(PREDICATE_SHOW_ALL_CUSTOMERS); } - //=========== Filtered Person List Accessors ============================================================= + @Override + public void addProperty(Property property) { + propertyBook.addProperty(property); + updateFilteredPropertyList(PREDICATE_SHOW_ALL_PROPERTIES); + } + + @Override + public void setCustomer(Customer target, Customer editedCustomer) { + requireAllNonNull(target, editedCustomer); + + addressBook.setCustomer(target, editedCustomer); + } + + @Override + public void setProperty(Property target, Property editedProperty) { + requireAllNonNull(target, editedProperty); + + propertyBook.setProperty(target, editedProperty); + } + + //=========== Filtered Customer List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Customer} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredCustomerList() { + return filteredCustomers; + } + + @Override + public void updateFilteredCustomerList(Predicate predicate) { + requireNonNull(predicate); + filteredCustomers.setPredicate(predicate); + } + + @Override + public void updateMatchedCustomerList(Customer targetCustomer, Predicate predicate) { + requireAllNonNull(targetCustomer, predicate); + Predicate custPredicate = new SameCustomerPredicate(targetCustomer); + filteredCustomers.setPredicate(custPredicate); + filteredProperties.setPredicate(predicate); + } + + //=========== Filtered Property List Accessors ============================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * Returns an unmodifiable view of the list of {@code Property} backed by the internal list of * {@code versionedAddressBook} */ @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; + public ObservableList getFilteredPropertyList() { + return filteredProperties; } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredPropertyList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredProperties.setPredicate(predicate); + } + + @Override + public void updateMatchedPropertyList(Property targetProperty, Predicate predicate) { + requireAllNonNull(targetProperty, predicate); + Predicate propPredicate = new SamePropertyPredicate(targetProperty); + filteredProperties.setPredicate(propPredicate); + filteredCustomers.setPredicate(predicate); } @Override @@ -141,8 +231,10 @@ public boolean equals(Object other) { ModelManager otherModelManager = (ModelManager) other; return addressBook.equals(otherModelManager.addressBook) + && propertyBook.equals(otherModelManager.propertyBook) && userPrefs.equals(otherModelManager.userPrefs) - && filteredPersons.equals(otherModelManager.filteredPersons); + && filteredCustomers.equals(otherModelManager.filteredCustomers) + && filteredProperties.equals(otherModelManager.filteredProperties); } } diff --git a/src/main/java/seedu/address/model/PropertyBook.java b/src/main/java/seedu/address/model/PropertyBook.java new file mode 100644 index 00000000000..2deef1d43e2 --- /dev/null +++ b/src/main/java/seedu/address/model/PropertyBook.java @@ -0,0 +1,133 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.property.Property; +import seedu.address.model.property.UniquePropertyList; + +/** + * Wraps all data at the PropertyMatch level + * Duplicates are not allowed (by .isSamePerson comparison) + * Duplicates are not allowed (by .isSameProperty comparison) + */ +public class PropertyBook implements ReadOnlyPropertyBook { + + private final UniquePropertyList properties; + + /* + * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ + { + properties = new UniquePropertyList(); + } + + public PropertyBook() {} + + /** + * Creates an AddressBook using the Persons in the {@code toBeCopied} + */ + public PropertyBook(ReadOnlyPropertyBook toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the property list with {@code properties}. + * {@code properties} must not contain duplicate properties. + */ + public void setProperties(List properties) { + this.properties.setProperties(properties); + } + + /** + * Resets the existing data of this {@code AddressBook} with {@code newData}. + */ + public void resetData(ReadOnlyPropertyBook newData) { + requireNonNull(newData); + + setProperties(newData.getPropertyList()); + } + + //// person-level operations + + /** + * Returns true if a property with the same identity as {@code property} exists in the address book. + */ + public boolean hasProperty(Property property) { + requireNonNull(property); + return properties.contains(property); + } + + /** + * Adds a property to the address book. + * The property must not already exist in the address book. + */ + public void addProperty(Property p) { + properties.add(p); + } + + + /** + * Replaces the given person {@code target} in the list with {@code editedProperty}. + * {@code target} must exist in the address book. + * The property identity of {@code editedProperty} must not + * be the same as another existing property in the address book. + */ + public void setProperty(Property target, Property editedProperty) { + requireNonNull(editedProperty); + + properties.setProperty(target, editedProperty); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeProperty(Property key) { + properties.remove(key); + } + + //// util methods + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("properties", properties) + .toString(); + } + + @Override + public ObservableList getPropertyList() { + return properties.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PropertyBook)) { + return false; + } + + PropertyBook otherPropertyBook = (PropertyBook) other; + return properties.equals(otherPropertyBook.properties) && properties.equals(otherPropertyBook.properties); + } + + @Override + public int hashCode() { + return properties.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..bc627f9e86e 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,7 @@ package seedu.address.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.customer.Customer; /** * Unmodifiable view of an address book @@ -9,9 +9,8 @@ public interface ReadOnlyAddressBook { /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. + * Returns an unmodifiable view of the customers list. + * This list will not contain any duplicate customers. */ - ObservableList getPersonList(); - + ObservableList getCustomerList(); } diff --git a/src/main/java/seedu/address/model/ReadOnlyPropertyBook.java b/src/main/java/seedu/address/model/ReadOnlyPropertyBook.java new file mode 100644 index 00000000000..9977a37808e --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyPropertyBook.java @@ -0,0 +1,16 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.property.Property; + +/** + * Unmodifiable view of an address book + */ +public interface ReadOnlyPropertyBook { + + /** + * Returns an unmodifiable view of the properties list. + * This list will not contain any duplicate properties. + */ + ObservableList getPropertyList(); +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java index befd58a4c73..11bf052fd9e 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java @@ -13,4 +13,6 @@ public interface ReadOnlyUserPrefs { Path getAddressBookFilePath(); + Path getPropertyBookFilePath(); + } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 6be655fb4c7..ae5a55d25c8 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -15,6 +15,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path propertyBookFilePath = Paths.get("data", "propertybook.json"); /** * Creates a {@code UserPrefs} with default values. @@ -36,6 +37,7 @@ public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setPropertyBookFilePath(newUserPrefs.getPropertyBookFilePath()); } public GuiSettings getGuiSettings() { @@ -50,11 +52,19 @@ public void setGuiSettings(GuiSettings guiSettings) { public Path getAddressBookFilePath() { return addressBookFilePath; } + public Path getPropertyBookFilePath() { + return propertyBookFilePath; + } + public void setAddressBookFilePath(Path addressBookFilePath) { requireNonNull(addressBookFilePath); this.addressBookFilePath = addressBookFilePath; } + public void setPropertyBookFilePath(Path propertyBookFilePath) { + requireNonNull(propertyBookFilePath); + this.propertyBookFilePath = propertyBookFilePath; + } @Override public boolean equals(Object other) { diff --git a/src/main/java/seedu/address/model/customer/Budget.java b/src/main/java/seedu/address/model/customer/Budget.java new file mode 100644 index 00000000000..fb6552d4581 --- /dev/null +++ b/src/main/java/seedu/address/model/customer/Budget.java @@ -0,0 +1,90 @@ +package seedu.address.model.customer; + +import static java.util.Objects.isNull; +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import seedu.address.model.property.Price; + +/** + * Represents a Customer's budget in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidBudget(String)} + */ +public class Budget { + + + public static final String MESSAGE_CONSTRAINTS = + "Budget should be \n" + + "- an integer\n" + + "- at least 10000\n" + + "- less than 1 trillion (1 000 000 000 000)\n" + + "- not start with 0\n"; + public static final String VALIDATION_REGEX = "[1-9]\\d{4,11}"; + public final Long amount; + public final String value; + + /** + * Constructs a {@code Budget}. + * + * @param budget A valid budget number. + */ + public Budget(String budget) { + requireNonNull(budget); + checkArgument(isValidBudget(budget), MESSAGE_CONSTRAINTS); + amount = Long.parseUnsignedLong(budget); + value = budget; + } + + /** + * Returns true if a given string is a valid budget. + */ + public static boolean isValidBudget(String test) { + return test.matches(VALIDATION_REGEX) + && Long.parseUnsignedLong(test) >= 10000 + && Long.parseUnsignedLong(test) < Long.parseUnsignedLong("1000000000000"); + } + + /** + * Returns true if the other budget is greater or equal this budget. + * + * @param other the other budget being compared + * @return whether the other budget is greater or equal to this budget + */ + public boolean isInRangeBudget(Budget other) { + return isNull(other) || amount >= other.amount; + } + + /** + * Convert the budget to a price object. + * + * @return a Price object that have the same amount with the budget. + */ + public Price convertToPrice() { + return new Price(value); + } + @Override + public String toString() { + return "$" + value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Budget)) { + return false; + } + + Budget otherBudget = (Budget) other; + return value.equals(otherBudget.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/customer/BudgetAndOneTagsPredicate.java b/src/main/java/seedu/address/model/customer/BudgetAndOneTagsPredicate.java new file mode 100644 index 00000000000..512397a6089 --- /dev/null +++ b/src/main/java/seedu/address/model/customer/BudgetAndOneTagsPredicate.java @@ -0,0 +1,59 @@ +package seedu.address.model.customer; + +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.tag.Tag; + +/** + * Tests that a {@code Customer}'s {@code Budget} and/or {@code Tags} are in range of the specified budget and/or tags. + */ +public class BudgetAndOneTagsPredicate implements Predicate { + private final Budget budget; + private final Set tags; + + /** + * Constructs a {@code BudgetAndOneTagsPredicate}. + * + * @param budget the specified budget if any + * @param tags the specified tags if any + */ + public BudgetAndOneTagsPredicate(Budget budget, Set tags) { + this.budget = budget; + this.tags = tags; + } + + @Override + public boolean test(Customer customer) { + if (tags.size() == 0) { + return customer.getBudget().isInRangeBudget(budget); + } else { + return tags.stream().anyMatch(tag -> customer.getTags().contains(tag)) + && customer.getBudget().isInRangeBudget(budget); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof BudgetAndOneTagsPredicate)) { + return false; + } + + BudgetAndOneTagsPredicate otherBudgetAndOneTagsPredicate = (BudgetAndOneTagsPredicate) other; + return budget.equals(otherBudgetAndOneTagsPredicate.budget) + && tags.equals(otherBudgetAndOneTagsPredicate.tags); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("budget", budget) + .add("tags", tags).toString(); + } +} diff --git a/src/main/java/seedu/address/model/customer/BudgetAndTagsInRangePredicate.java b/src/main/java/seedu/address/model/customer/BudgetAndTagsInRangePredicate.java new file mode 100644 index 00000000000..2f6284ec137 --- /dev/null +++ b/src/main/java/seedu/address/model/customer/BudgetAndTagsInRangePredicate.java @@ -0,0 +1,58 @@ +package seedu.address.model.customer; + +import static java.util.Objects.isNull; + +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.tag.Tag; + +/** + * Tests that a {@code Customer}'s {@code Budget} + * and/or minimum one {@code Tags} are in range of the specified budget and/or tags. + */ +public class BudgetAndTagsInRangePredicate implements Predicate { + private final Budget budget; + private final Set tags; + + /** + * Constructs a {@code BudgetAndTagsInRangePredicate}. + * + * @param budget the specified budget if any + * @param tags the specified tags if any + */ + public BudgetAndTagsInRangePredicate(Budget budget, Set tags) { + this.budget = budget; + this.tags = tags; + } + + @Override + public boolean test(Customer customer) { + return (isNull(tags) || customer.getTags().containsAll(tags)) + && customer.getBudget().isInRangeBudget(budget); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof BudgetAndTagsInRangePredicate)) { + return false; + } + + BudgetAndTagsInRangePredicate otherBudgetAndTagsInRangePredicate = (BudgetAndTagsInRangePredicate) other; + return budget.equals(otherBudgetAndTagsInRangePredicate.budget) + && tags.equals(otherBudgetAndTagsInRangePredicate.tags); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("budget", budget) + .add("tags", tags).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/customer/Customer.java similarity index 58% rename from src/main/java/seedu/address/model/person/Person.java rename to src/main/java/seedu/address/model/customer/Customer.java index abe8c46b535..3a18d6ba720 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/customer/Customer.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; @@ -11,10 +11,10 @@ import seedu.address.model.tag.Tag; /** - * Represents a Person in the address book. + * Represents a Customer in the address book. * Guarantees: details are present and not null, field values are validated, immutable. */ -public class Person { +public class Customer { // Identity fields private final Name name; @@ -22,18 +22,18 @@ public class Person { private final Email email; // Data fields - private final Address address; + private final Budget budget; private final Set tags = new HashSet<>(); /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Customer(Name name, Phone phone, Email email, Budget budget, Set tags) { + requireAllNonNull(name, phone, email, budget, tags); this.name = name; this.phone = phone; this.email = email; - this.address = address; + this.budget = budget; this.tags.addAll(tags); } @@ -49,8 +49,8 @@ public Email getEmail() { return email; } - public Address getAddress() { - return address; + public Budget getBudget() { + return budget; } /** @@ -62,21 +62,20 @@ public Set getTags() { } /** - * Returns true if both persons have the same name. - * This defines a weaker notion of equality between two persons. + * Returns true if both customers have the same phone. + * This defines a weaker notion of equality between two customers. */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { + public boolean isSameCustomer(Customer otherCustomer) { + if (otherCustomer == this) { return true; } - return otherPerson != null - && otherPerson.getName().equals(getName()); + return otherCustomer != null && otherCustomer.getPhone().equals(getPhone()); } /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. + * Returns true if both customers have the same identity and data fields. + * This defines a stronger notion of equality between two customers. */ @Override public boolean equals(Object other) { @@ -85,22 +84,22 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof Person)) { + if (!(other instanceof Customer)) { return false; } - Person otherPerson = (Person) other; - return name.equals(otherPerson.name) - && phone.equals(otherPerson.phone) - && email.equals(otherPerson.email) - && address.equals(otherPerson.address) - && tags.equals(otherPerson.tags); + Customer otherCustomer = (Customer) other; + return name.equals(otherCustomer.name) + && phone.equals(otherCustomer.phone) + && email.equals(otherCustomer.email) + && budget.equals(otherCustomer.budget) + && tags.equals(otherCustomer.tags); } @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, budget, tags); } @Override @@ -109,7 +108,7 @@ public String toString() { .add("name", name) .add("phone", phone) .add("email", email) - .add("address", address) + .add("budget", budget) .add("tags", tags) .toString(); } diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/customer/Email.java similarity index 96% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/seedu/address/model/customer/Email.java index c62e512bc29..9a44440e703 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/customer/Email.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Customer's email in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/customer/Name.java similarity index 94% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/customer/Name.java index 173f15b9b00..bf73e3dac4a 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/customer/Name.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Customer's name in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/customer/NameContainsKeywordsPredicate.java similarity index 75% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/customer/NameContainsKeywordsPredicate.java index 62d19be2977..5b361bffaf4 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/customer/NameContainsKeywordsPredicate.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import java.util.List; import java.util.function.Predicate; @@ -7,9 +7,9 @@ import seedu.address.commons.util.ToStringBuilder; /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Customer}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; public NameContainsKeywordsPredicate(List keywords) { @@ -17,9 +17,10 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { + public boolean test(Customer customer) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.startsWithWordIgnoreCaseWithoutFullMatch(customer.getName().fullName, + keyword)); } @Override diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/customer/Phone.java similarity index 83% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/address/model/customer/Phone.java index d733f63d739..2289d0c6b98 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/customer/Phone.java @@ -1,18 +1,18 @@ -package seedu.address.model.person; +package seedu.address.model.customer; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Customer's phone number in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; + "Phone numbers should start with 6, 8 or 9, and it should be 8 digits long"; + public static final String VALIDATION_REGEX = "(6|8|9)\\d{7}"; public final String value; /** diff --git a/src/main/java/seedu/address/model/customer/SameCustomerPredicate.java b/src/main/java/seedu/address/model/customer/SameCustomerPredicate.java new file mode 100644 index 00000000000..af08c7b5ea0 --- /dev/null +++ b/src/main/java/seedu/address/model/customer/SameCustomerPredicate.java @@ -0,0 +1,48 @@ +package seedu.address.model.customer; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Customer} is same with the given customer. + */ +public class SameCustomerPredicate implements Predicate { + + private final Customer customer; + + /** + * Constructs a {@code SameCustomerPredicate}. + * + * @param customer the specified customer + */ + public SameCustomerPredicate(Customer customer) { + this.customer = customer; + } + + @Override + public boolean test(Customer targetCustomer) { + return customer.equals(targetCustomer); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SameCustomerPredicate)) { + return false; + } + + SameCustomerPredicate otherSameCustomerPredicate = (SameCustomerPredicate) other; + return this.customer == otherSameCustomerPredicate.customer; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("Customer", customer).toString(); + } +} diff --git a/src/main/java/seedu/address/model/customer/UniqueCustomerList.java b/src/main/java/seedu/address/model/customer/UniqueCustomerList.java new file mode 100644 index 00000000000..afca2e1ff7b --- /dev/null +++ b/src/main/java/seedu/address/model/customer/UniqueCustomerList.java @@ -0,0 +1,151 @@ +package seedu.address.model.customer; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.customer.exceptions.CustomerNotFoundException; +import seedu.address.model.customer.exceptions.DuplicateCustomerException; + +/** + * A list of customers that enforces uniqueness between its elements and does not allow nulls. + * A customer is considered unique by comparing using {@code Customer#isSameCustomer(Customer)}. + * As such, adding and updating of customers uses Customer#isSameCustomer(Customer) + * for equality so as to ensure that the customer being added or updated is unique in terms of identity + * in the UniqueCustomerList. However, the removal of a customer uses Customer#equals(Object) + * so as to ensure that the customer with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Customer#isSameCustomer(Customer) + */ +public class UniqueCustomerList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent customer as the given argument. + */ + public boolean contains(Customer toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameCustomer); + } + + /** + * Adds a customer to the list. + * The customer must not already exist in the list. + */ + public void add(Customer toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateCustomerException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the customer {@code target} in the list with {@code editedCustomer}. + * {@code target} must exist in the list. + * The customer identity of {@code editedCustomer} must not be the same as another existing customer in the list. + */ + public void setCustomer(Customer target, Customer editedCustomer) { + requireAllNonNull(target, editedCustomer); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new CustomerNotFoundException(); + } + + if (!target.isSameCustomer(editedCustomer) && contains(editedCustomer)) { + throw new DuplicateCustomerException(); + } + + internalList.set(index, editedCustomer); + } + + /** + * Removes the equivalent customer from the list. + * The customer must exist in the list. + */ + public void remove(Customer toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new CustomerNotFoundException(); + } + } + + public void setCustomers(UniqueCustomerList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code customers}. + * {@code customers} must not contain duplicate customers. + */ + public void setCustomers(List customers) { + requireAllNonNull(customers); + if (!customersAreUnique(customers)) { + throw new DuplicateCustomerException(); + } + + internalList.setAll(customers); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueCustomerList)) { + return false; + } + + UniqueCustomerList otherUniqueCustomerList = (UniqueCustomerList) other; + return internalList.equals(otherUniqueCustomerList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code customers} contains only unique customers. + */ + private boolean customersAreUnique(List customers) { + for (int i = 0; i < customers.size() - 1; i++) { + for (int j = i + 1; j < customers.size(); j++) { + if (customers.get(i).isSameCustomer(customers.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/customer/exceptions/CustomerNotFoundException.java b/src/main/java/seedu/address/model/customer/exceptions/CustomerNotFoundException.java new file mode 100644 index 00000000000..10aa090a3a5 --- /dev/null +++ b/src/main/java/seedu/address/model/customer/exceptions/CustomerNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.customer.exceptions; + +/** + * Signals that the operation is unable to find the specified customer. + */ +public class CustomerNotFoundException extends RuntimeException { + public CustomerNotFoundException() { + super("Unable to find specified customer"); + } +} diff --git a/src/main/java/seedu/address/model/customer/exceptions/DuplicateCustomerException.java b/src/main/java/seedu/address/model/customer/exceptions/DuplicateCustomerException.java new file mode 100644 index 00000000000..7f039e26e0a --- /dev/null +++ b/src/main/java/seedu/address/model/customer/exceptions/DuplicateCustomerException.java @@ -0,0 +1,11 @@ +package seedu.address.model.customer.exceptions; + +/** + * Signals that the operation will result in duplicate Customers + * (Customers are considered duplicates if they have the same identity). + */ +public class DuplicateCustomerException extends RuntimeException { + public DuplicateCustomerException() { + super("Operation would result in duplicate customers"); + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index cc0a68d79f9..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,150 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof UniquePersonList)) { - return false; - } - - UniquePersonList otherUniquePersonList = (UniquePersonList) other; - return internalList.equals(otherUniquePersonList.internalList); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - @Override - public String toString() { - return internalList.toString(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f59442..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca7..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/property/Price.java b/src/main/java/seedu/address/model/property/Price.java new file mode 100644 index 00000000000..972fc0c1467 --- /dev/null +++ b/src/main/java/seedu/address/model/property/Price.java @@ -0,0 +1,89 @@ +package seedu.address.model.property; + +import static java.util.Objects.isNull; +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import seedu.address.model.customer.Budget; + +/** + * Represents a Price in the Property book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Price { + + public static final String MESSAGE_CONSTRAINTS = + "Price should be \n" + + "- an integer\n" + + "- at least 10000\n" + + "- less than 1 trillion (1 000 000 000 000)\n" + + "- not start with 0\n"; + public static final String VALIDATION_REGEX = "[1-9]\\d{4,11}"; + public final Long amount; + public final String value; + + /** + * Constructs a {@code Phone}. + * + * @param price A valid price number. + */ + public Price(String price) { + requireNonNull(price); + checkArgument(isValidPrice(price), MESSAGE_CONSTRAINTS); + amount = Long.parseUnsignedLong(price); + value = price; + } + + /** + * Returns true if a given string is a valid price. + */ + public static boolean isValidPrice(String test) { + return test.matches(VALIDATION_REGEX) + && Long.parseUnsignedLong(test) >= 10000 + && Long.parseUnsignedLong(test) < Long.parseUnsignedLong("1000000000000"); + } + + /** + * Returns true if the other price is lower or equal this price + * + * @param other the other budget being compared + * @return whether the other budget is lower or equal to this budget + */ + public boolean isInRangePrice(Price other) { + return isNull(other) || amount <= other.amount; + } + + /** + * Convert the price to a budget object. + * + * @return a budget object that have the same amount with the price. + */ + public Budget convertToBudget() { + return new Budget(value); + } + @Override + public String toString() { + return "$" + value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Price)) { + return false; + } + + Price otherPrice = (Price) other; + return value.equals(otherPrice.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/property/PriceAndOneTagsPredicate.java b/src/main/java/seedu/address/model/property/PriceAndOneTagsPredicate.java new file mode 100644 index 00000000000..20bf3b281bf --- /dev/null +++ b/src/main/java/seedu/address/model/property/PriceAndOneTagsPredicate.java @@ -0,0 +1,60 @@ +package seedu.address.model.property; + +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.tag.Tag; + +/** + * Tests that a {@code Property}'s {@code Price} + * and/or minimal one {@code Tags} are in range of the specified price and/or tags. + */ +public class PriceAndOneTagsPredicate implements Predicate { + private final Price price; + private final Set tags; + + /** + * Constructs a {@code PriceAndOneTagsPredicate}. + * + * @param price the specified price if any + * @param tags the specified tags if any + */ + public PriceAndOneTagsPredicate(Price price, Set tags) { + this.price = price; + this.tags = tags; + } + + @Override + public boolean test(Property property) { + if (tags.size() == 0) { + return property.getPrice().isInRangePrice(price); + } else { + return tags.stream().anyMatch(tag -> property.getTags().contains(tag)) + && property.getPrice().isInRangePrice(price); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PriceAndOneTagsPredicate)) { + return false; + } + + PriceAndOneTagsPredicate otherBudgetAndOneTagsPredicate = (PriceAndOneTagsPredicate) other; + return price.equals(otherBudgetAndOneTagsPredicate.price) + && tags.equals(otherBudgetAndOneTagsPredicate.tags); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("price", price) + .add("tags", tags).toString(); + } +} diff --git a/src/main/java/seedu/address/model/property/PriceAndTagsInRangePredicate.java b/src/main/java/seedu/address/model/property/PriceAndTagsInRangePredicate.java new file mode 100644 index 00000000000..46077189216 --- /dev/null +++ b/src/main/java/seedu/address/model/property/PriceAndTagsInRangePredicate.java @@ -0,0 +1,57 @@ +package seedu.address.model.property; + +import static java.util.Objects.isNull; + +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.tag.Tag; + +/** + * Tests that a {@code Property}'s {@code Price} and/or {@code Tags} are in range of the specified price and/or tags. + */ +public class PriceAndTagsInRangePredicate implements Predicate { + private final Price price; + private final Set tags; + + /** + * Constructs a {@code PriceAndTagsInRangePredicate}. + * + * @param price the specified price if any + * @param tags the specified tags if any + */ + public PriceAndTagsInRangePredicate(Price price, Set tags) { + this.price = price; + this.tags = tags; + } + + @Override + public boolean test(Property property) { + return (isNull(tags) || property.getTags().containsAll(tags)) + && property.getPrice().isInRangePrice(price); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PriceAndTagsInRangePredicate)) { + return false; + } + + PriceAndTagsInRangePredicate otherBudgetAndTagsInRangePredicate = (PriceAndTagsInRangePredicate) other; + return price.equals(otherBudgetAndTagsInRangePredicate.price) + && tags.equals(otherBudgetAndTagsInRangePredicate.tags); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("price", price) + .add("tags", tags).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/property/PropAddress.java similarity index 63% rename from src/main/java/seedu/address/model/person/Address.java rename to src/main/java/seedu/address/model/property/PropAddress.java index 469a2cc9a1e..65858919bcf 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/property/PropAddress.java @@ -1,21 +1,22 @@ -package seedu.address.model.person; +package seedu.address.model.property; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's address in the address book. + * Represents a Property's address in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} */ -public class Address { +public class PropAddress { - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; + public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values except slashes," + + "and it should not be blank."; /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. + * The first character of the property name must not be a whitespace or slash, + * otherwise " " (a blank string) or "/" becomes a valid input. */ - public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final String VALIDATION_REGEX = "[^\\s/][^/]*"; public final String value; @@ -24,7 +25,7 @@ public class Address { * * @param address A valid address. */ - public Address(String address) { + public PropAddress(String address) { requireNonNull(address); checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); value = address; @@ -49,11 +50,11 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof Address)) { + if (!(other instanceof seedu.address.model.property.PropAddress)) { return false; } - Address otherAddress = (Address) other; + seedu.address.model.property.PropAddress otherAddress = (seedu.address.model.property.PropAddress) other; return value.equals(otherAddress.value); } diff --git a/src/main/java/seedu/address/model/property/PropName.java b/src/main/java/seedu/address/model/property/PropName.java new file mode 100644 index 00000000000..036ec95607f --- /dev/null +++ b/src/main/java/seedu/address/model/property/PropName.java @@ -0,0 +1,66 @@ +package seedu.address.model.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Property's name in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} + */ +public class PropName { + + public static final String MESSAGE_CONSTRAINTS = + "Names can take any values except slashes, and it should not be blank."; + + /* + * The first character of the property name must not be a whitespace or slash, + * otherwise " " (a blank string) or "/" becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s/][^/]*"; + public final String fullName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public PropName(String name) { + requireNonNull(name); + checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); + fullName = name; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidName(String test) { + return test.matches(VALIDATION_REGEX); + } + + + @Override + public String toString() { + return fullName; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof seedu.address.model.property.PropName)) { + return false; + } + + seedu.address.model.property.PropName otherName = (seedu.address.model.property.PropName) other; + return fullName.equals(otherName.fullName); + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/property/PropNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/property/PropNameContainsKeywordsPredicate.java new file mode 100644 index 00000000000..d97c261d499 --- /dev/null +++ b/src/main/java/seedu/address/model/property/PropNameContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.property; + +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 Name} matches any of the keywords given. + */ +public class PropNameContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public PropNameContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Property property) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.startsWithWordIgnoreCaseWithoutFullMatch(property.getName().fullName, + keyword)); + } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PropNameContainsKeywordsPredicate)) { + return false; + } + + PropNameContainsKeywordsPredicate otherNameContainsKeywordsPredicate = + (PropNameContainsKeywordsPredicate) other; + return keywords.equals(otherNameContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/property/PropPhone.java b/src/main/java/seedu/address/model/property/PropPhone.java new file mode 100644 index 00000000000..4d3a1170bc9 --- /dev/null +++ b/src/main/java/seedu/address/model/property/PropPhone.java @@ -0,0 +1,61 @@ +package seedu.address.model.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Property's phone number in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} + */ +public class PropPhone { + + + public static final String MESSAGE_CONSTRAINTS = + "Phone numbers should start with 6, 8 or 9, and it should be 8 digits long"; + public static final String VALIDATION_REGEX = "(6|8|9)\\d{7}"; + public final String value; + + /** + * Constructs a {@code Phone}. + * + * @param phone A valid phone number. + */ + public PropPhone(String phone) { + requireNonNull(phone); + checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS); + value = phone; + } + + /** + * Returns true if a given string is a valid phone number. + */ + public static boolean isValidPhone(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PropPhone)) { + return false; + } + + PropPhone otherPropPhone = (PropPhone) other; + return value.equals(otherPropPhone.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/property/Property.java b/src/main/java/seedu/address/model/property/Property.java new file mode 100644 index 00000000000..52dd724fa37 --- /dev/null +++ b/src/main/java/seedu/address/model/property/Property.java @@ -0,0 +1,115 @@ +package seedu.address.model.property; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.tag.Tag; + +/** + * Represents a Property in the property book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Property { + + // Identity fields + private final PropName propName; + private final PropAddress propAddress; + private final PropPhone propPhone; + private final Price price; + // Data fields + private final Set tags = new HashSet<>(); + + /** + * Every field must be present and not null. + */ + public Property(PropName propName, PropAddress propAddress, PropPhone propPhone, Price price, Set tags) { + requireAllNonNull(propName, propPhone, price, propAddress, tags); + this.propName = propName; + this.propPhone = propPhone; + this.price = price; + this.propAddress = propAddress; + this.tags.addAll(tags); + } + + public PropName getName() { + return propName; + } + + public PropPhone getPhone() { + return propPhone; + } + + public Price getPrice() { + return price; + } + + public PropAddress getAddress() { + return propAddress; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + /** + * Returns true if both properties have the same address. + * This defines a weaker notion of equality between two persons. + */ + public boolean isSameProperty(Property otherProperty) { + if (otherProperty == this) { + return true; + } + + return otherProperty != null && otherProperty.getAddress().equals(getAddress()); + } + + /** + * Returns true if both properties have the same identity and data fields. + * This defines a stronger notion of equality between two persons. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Property)) { + return false; + } + + Property otherProperty = (Property) other; + return propName.equals(otherProperty.propName) + && propPhone.equals(otherProperty.propPhone) + && price.equals(otherProperty.price) + && propAddress.equals(otherProperty.propAddress) + && tags.equals(otherProperty.tags); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(propName, propAddress, propPhone, price, tags); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("name", propName) + .add("address", propAddress) + .add("phone", propPhone) + .add("price", price) + .add("tags", tags) + .toString(); + } + +} diff --git a/src/main/java/seedu/address/model/property/SamePropertyPredicate.java b/src/main/java/seedu/address/model/property/SamePropertyPredicate.java new file mode 100644 index 00000000000..16d6570ee50 --- /dev/null +++ b/src/main/java/seedu/address/model/property/SamePropertyPredicate.java @@ -0,0 +1,48 @@ +package seedu.address.model.property; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Property} is same with the given property. + */ +public class SamePropertyPredicate implements Predicate { + + private final Property property; + + /** + * Constructs a {@code SamePropertyPredicate}. + * + * @param property the specified property + */ + public SamePropertyPredicate(Property property) { + this.property = property; + } + + @Override + public boolean test(Property targetProperty) { + return property.equals(targetProperty); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SamePropertyPredicate)) { + return false; + } + + SamePropertyPredicate otherSameCustomerPredicate = (SamePropertyPredicate) other; + return this.property == otherSameCustomerPredicate.property; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("Property", property).toString(); + } +} diff --git a/src/main/java/seedu/address/model/property/UniquePropertyList.java b/src/main/java/seedu/address/model/property/UniquePropertyList.java new file mode 100644 index 00000000000..ac2668fb2b5 --- /dev/null +++ b/src/main/java/seedu/address/model/property/UniquePropertyList.java @@ -0,0 +1,150 @@ +package seedu.address.model.property; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.property.exceptions.DuplicatePropertyException; +import seedu.address.model.property.exceptions.PropertyNotFoundException; + +/** + * A list of persons that enforces uniqueness between its elements and does not allow nulls. + * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of + * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is + * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so + * as to ensure that the person with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Property#isSameProperty(Property) + */ +public class UniquePropertyList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent property as the given argument. + */ + public boolean contains(Property toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameProperty); + } + + /** + * Adds a property to the list. + * The property must not already exist in the list. + */ + public void add(Property toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePropertyException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the property {@code target} in the list with {@code editedProperty}. + * {@code target} must exist in the list. + * The property identity of {@code editedProperty} must not be the same as another existing property in the list. + */ + public void setProperty(Property target, Property editedProperty) { + requireAllNonNull(target, editedProperty); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PropertyNotFoundException(); + } + + if (!target.isSameProperty(editedProperty) && contains(editedProperty)) { + throw new DuplicatePropertyException(); + } + + internalList.set(index, editedProperty); + } + + /** + * Removes the equivalent property from the list. + * The property must exist in the list. + */ + public void remove(Property toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new PropertyNotFoundException(); + } + } + + public void setProperties(UniquePropertyList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code properties}. + * {@code properties} must not contain duplicate properties. + */ + public void setProperties(List properties) { + requireAllNonNull(properties); + if (!propertiesAreUnique(properties)) { + throw new DuplicatePropertyException(); + } + + internalList.setAll(properties); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniquePropertyList)) { + return false; + } + + UniquePropertyList otherUniquePropertyList = (UniquePropertyList) other; + return internalList.equals(otherUniquePropertyList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code properties} contains only unique properties. + */ + private boolean propertiesAreUnique(List properties) { + for (int i = 0; i < properties.size() - 1; i++) { + for (int j = i + 1; j < properties.size(); j++) { + if (properties.get(i).isSameProperty(properties.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/property/exceptions/DuplicatePropertyException.java b/src/main/java/seedu/address/model/property/exceptions/DuplicatePropertyException.java new file mode 100644 index 00000000000..7191e021942 --- /dev/null +++ b/src/main/java/seedu/address/model/property/exceptions/DuplicatePropertyException.java @@ -0,0 +1,11 @@ +package seedu.address.model.property.exceptions; + +/** + * Signals that the operation will result in duplicate Properties (Properties are considered duplicates if they + * have the same identity). + */ +public class DuplicatePropertyException extends RuntimeException { + public DuplicatePropertyException() { + super("Operation would result in duplicate property"); + } +} diff --git a/src/main/java/seedu/address/model/property/exceptions/PropertyNotFoundException.java b/src/main/java/seedu/address/model/property/exceptions/PropertyNotFoundException.java new file mode 100644 index 00000000000..5021986979b --- /dev/null +++ b/src/main/java/seedu/address/model/property/exceptions/PropertyNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.property.exceptions; + +/** + * Signals that the operation is unable to find the specified person. + */ +public class PropertyNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index f1a0d4e233b..d28ee7efe78 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -22,7 +22,7 @@ public class Tag { public Tag(String tagName) { requireNonNull(tagName); checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; + this.tagName = tagName.toLowerCase(); } /** diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..813b428d7dc 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -5,49 +5,86 @@ import java.util.stream.Collectors; import seedu.address.model.AddressBook; +import seedu.address.model.PropertyBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.ReadOnlyPropertyBook; +import seedu.address.model.customer.Budget; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; +import seedu.address.model.property.Price; +import seedu.address.model.property.PropAddress; +import seedu.address.model.property.PropName; +import seedu.address.model.property.PropPhone; +import seedu.address.model.property.Property; import seedu.address.model.tag.Tag; /** * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + public static Customer[] getSampleCustomers() { + return new Customer[] { + new Customer(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Budget("100000"), + getTagSet("bright")), + new Customer(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), + new Budget("999000"), + getTagSet("white", "square")), + new Customer(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), + new Budget("40000000"), getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + new Customer(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), + new Budget("7600000"), getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new Customer(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), + new Budget("50000"), + getTagSet("small")), + new Customer(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), + new Budget("200000"), + getTagSet("city", "center")) + }; + } + + public static Property[] getSampleProperties() { + return new Property[] { + new Property(new PropName("Aquavista"), new PropAddress("123 Orchid Lane, Singapore 456789"), + new PropPhone("94351253"), new Price("123456"), + getTagSet("pink")), + new Property(new PropName("Skyvista"), new PropAddress("456 Sapphire Avenue, Singapore 987654"), + new PropPhone("98765432"), new Price("8880000"), + getTagSet("square", "garden")), + new Property(new PropName("Horizonview"), new PropAddress("789 Palm Grove Road, Singapore 321012"), + new PropPhone("95352563"), new Price("400000"), + getTagSet()), + new Property(new PropName("Luxeloft"), new PropAddress("234 Amber Crescent, Singapore 567890"), + new PropPhone("87652533"), new Price("100000"), + getTagSet("garage")), + new Property(new PropName("Riveria"), new PropAddress("567 Maple Lane, Singapore 109876"), + new PropPhone("94821224"), new Price("4000000"), + getTagSet()), + new Property(new PropName("Azure"), new PropAddress("202 Shoreline Street, Singapore 654321"), + new PropPhone("94821442"), new Price("321950"), + getTagSet()) }; } public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + for (Customer sampleCustomer : getSampleCustomers()) { + sampleAb.addCustomer(sampleCustomer); } return sampleAb; } + public static ReadOnlyPropertyBook getSamplePropertyBook() { + PropertyBook samplePb = new PropertyBook(); + for (Property sampleProperty : getSampleProperties()) { + samplePb.addProperty(sampleProperty); + } + return samplePb; + } /** * Returns a tag set containing the list of strings given. */ diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedCustomer.java similarity index 56% rename from src/main/java/seedu/address/storage/JsonAdaptedPerson.java rename to src/main/java/seedu/address/storage/JsonAdaptedCustomer.java index bd1ca0f56c8..7cddc293750 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedCustomer.java @@ -10,64 +10,64 @@ import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.customer.Budget; +import seedu.address.model.customer.Customer; +import seedu.address.model.customer.Email; +import seedu.address.model.customer.Name; +import seedu.address.model.customer.Phone; import seedu.address.model.tag.Tag; /** - * Jackson-friendly version of {@link Person}. + * Jackson-friendly version of {@link Customer}. */ -class JsonAdaptedPerson { +class JsonAdaptedCustomer { - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Customer's %s field is missing!"; private final String name; private final String phone; private final String email; - private final String address; + private final String budget; private final List tags = new ArrayList<>(); /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. + * Constructs a {@code JsonAdaptedCustomer} with the given customer details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tags") List tags) { + public JsonAdaptedCustomer(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + @JsonProperty("email") String email, @JsonProperty("budget") String budget, + @JsonProperty("tags") List tags) { this.name = name; this.phone = phone; this.email = email; - this.address = address; + this.budget = budget; if (tags != null) { this.tags.addAll(tags); } } /** - * Converts a given {@code Person} into this class for Jackson use. + * Converts a given {@code Customer} into this class for Jackson use. */ - public JsonAdaptedPerson(Person source) { + public JsonAdaptedCustomer(Customer source) { name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; - address = source.getAddress().value; + budget = source.getBudget().value; tags.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); } /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * Converts this Jackson-friendly adapted customer object into the model's {@code Customer} object. * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. + * @throws IllegalValueException if there were any data constraints violated in the adapted customer. */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); + public Customer toModelType() throws IllegalValueException { + final List customerTags = new ArrayList<>(); for (JsonAdaptedTag tag : tags) { - personTags.add(tag.toModelType()); + customerTags.add(tag.toModelType()); } if (name == null) { @@ -94,16 +94,16 @@ public Person toModelType() throws IllegalValueException { } final Email modelEmail = new Email(email); - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + if (budget == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Budget.class.getSimpleName())); } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + if (!Budget.isValidBudget(budget)) { + throw new IllegalValueException(Budget.MESSAGE_CONSTRAINTS); } - final Address modelAddress = new Address(address); + final Budget modelBudget = new Budget(budget); - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + final Set modelTags = new HashSet<>(customerTags); + return new Customer(modelName, modelPhone, modelEmail, modelBudget, modelTags); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedProperty.java b/src/main/java/seedu/address/storage/JsonAdaptedProperty.java new file mode 100644 index 00000000000..10fdfc8bfe9 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedProperty.java @@ -0,0 +1,115 @@ +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.property.Price; +import seedu.address.model.property.PropAddress; +import seedu.address.model.property.PropName; +import seedu.address.model.property.PropPhone; +import seedu.address.model.property.Property; +import seedu.address.model.tag.Tag; + + +/** + * Jackson-friendly version of {@link Property}. + */ +class JsonAdaptedProperty { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Property's %s field is missing!"; + + private final String name; + + private final String address; + private final String phone; + private final String price; + private final List tags = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedPerson} with the given person details. + */ + @JsonCreator + public JsonAdaptedProperty(@JsonProperty("name") String name, @JsonProperty("address") String address, + @JsonProperty("phone") String phone, @JsonProperty("price") String price, + @JsonProperty("tags") List tags) { + this.name = name; + this.address = address; + this.phone = phone; + this.price = price; + if (tags != null) { + this.tags.addAll(tags); + } + } + + /** + * Converts a given {@code Person} into this class for Jackson use. + */ + public JsonAdaptedProperty(Property source) { + name = source.getName().fullName; + phone = source.getPhone().value; + price = source.getPrice().value; + address = source.getAddress().value; + tags.addAll(source.getTags().stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person. + */ + public Property 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, PropName.class.getSimpleName())); + } + if (!PropName.isValidName(name)) { + throw new IllegalValueException(PropName.MESSAGE_CONSTRAINTS); + } + final PropName modelPropName = new PropName(name); + + if (address == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, PropAddress.class.getSimpleName())); + } + if (!PropAddress.isValidAddress(address)) { + throw new IllegalValueException(PropAddress.MESSAGE_CONSTRAINTS); + } + final PropAddress modelPropAddress = new PropAddress(address); + + if (phone == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, PropPhone.class.getSimpleName())); + } + if (!PropPhone.isValidPhone(phone)) { + throw new IllegalValueException(PropPhone.MESSAGE_CONSTRAINTS); + } + final PropPhone modelPropPhone = new PropPhone(phone); + + if (price == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Price.class.getSimpleName())); + } + if (!Price.isValidPrice(price)) { + throw new IllegalValueException(Price.MESSAGE_CONSTRAINTS); + } + final Price modelPrice = new Price(price); + + final Set modelTags = new HashSet<>(personTags); + return new Property(modelPropName, modelPropAddress, modelPropPhone, modelPrice, modelTags); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonPropertyBookStorage.java b/src/main/java/seedu/address/storage/JsonPropertyBookStorage.java new file mode 100644 index 00000000000..239f39f93dd --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonPropertyBookStorage.java @@ -0,0 +1,81 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.DataLoadingException; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.FileUtil; +import seedu.address.commons.util.JsonUtil; +import seedu.address.model.ReadOnlyPropertyBook; + + +/** + * A class to access PropertyBook data stored as a json file on the hard disk. + */ +public class JsonPropertyBookStorage implements PropertyBookStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonPropertyBookStorage.class); + + private Path filePath; + + public JsonPropertyBookStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getPropertyBookFilePath() { + return filePath; + } + + @Override + public Optional readPropertyBook() throws DataLoadingException { + return readPropertyBook(filePath); + } + + /** + * Similar to {@link #readPropertyBook()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataLoadingException if loading the data from storage failed. + */ + public Optional readPropertyBook(Path filePath) throws DataLoadingException { + requireNonNull(filePath); + + Optional jsonPropertyBook = JsonUtil.readJsonFile( + filePath, JsonSerializablePropertyBook.class); + if (!jsonPropertyBook.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonPropertyBook.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataLoadingException(ive); + } + } + + @Override + public void savePropertyBook(ReadOnlyPropertyBook propertyBook) throws IOException { + savePropertyBook(propertyBook, filePath); + } + + /** + * Similar to {@link #savePropertyBook(ReadOnlyPropertyBook)}. + * + * @param filePath location of the data. Cannot be null. + */ + public void savePropertyBook(ReadOnlyPropertyBook propertyBook, Path filePath) throws IOException { + requireNonNull(propertyBook); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializablePropertyBook(propertyBook), filePath); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..2568e151f1f 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -11,7 +11,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.customer.Customer; /** * An Immutable AddressBook that is serializable to JSON format. @@ -19,16 +19,16 @@ @JsonRootName(value = "addressbook") class JsonSerializableAddressBook { - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_CUSTOMER = "Customers list contains duplicate customer(s)."; - private final List persons = new ArrayList<>(); + private final List customers = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given customers. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); + public JsonSerializableAddressBook(@JsonProperty("customers") List customers) { + this.customers.addAll(customers); } /** @@ -37,7 +37,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List properties = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializablePropertyBook} with the given properties. + */ + @JsonCreator + public JsonSerializablePropertyBook(@JsonProperty("properties") List properties) { + this.properties.addAll(properties); + } + + /** + * Converts a given {@code ReadOnlyPropertyBook} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializablePropertyBook}. + */ + public JsonSerializablePropertyBook(ReadOnlyPropertyBook source) { + properties.addAll(source.getPropertyList().stream().map(JsonAdaptedProperty::new).collect(Collectors.toList())); + } + + /** + * Converts this property book into the model's {@code PropertyBook} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public PropertyBook toModelType() throws IllegalValueException { + PropertyBook propertyBook = new PropertyBook(); + for (JsonAdaptedProperty jsonAdaptedProperty : properties) { + Property property = jsonAdaptedProperty.toModelType(); + if (propertyBook.hasProperty(property)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_PROPERTY); + } + propertyBook.addProperty(property); + } + return propertyBook; + } + +} diff --git a/src/main/java/seedu/address/storage/PropertyBookStorage.java b/src/main/java/seedu/address/storage/PropertyBookStorage.java new file mode 100644 index 00000000000..9c3b008ccf2 --- /dev/null +++ b/src/main/java/seedu/address/storage/PropertyBookStorage.java @@ -0,0 +1,46 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataLoadingException; +import seedu.address.model.ReadOnlyPropertyBook; + + +/** + * Represents a storage for {@link seedu.address.model.AddressBook}. + */ +public interface PropertyBookStorage { + + /** + * Returns the file path of the data file. + */ + Path getPropertyBookFilePath(); + + /** + * Returns AddressBook data as a {@link ReadOnlyPropertyBook}. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataLoadingException if loading the data from storage failed. + */ + Optional readPropertyBook() throws DataLoadingException; + + /** + * @see #getPropertyBookFilePath() + */ + Optional readPropertyBook(Path filePath) throws DataLoadingException; + + /** + * Saves the given {@link ReadOnlyPropertyBook} to the storage. + * @param propertyBook cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void savePropertyBook(ReadOnlyPropertyBook propertyBook) throws IOException; + + /** + * @see #savePropertyBook(ReadOnlyPropertyBook) + */ + void savePropertyBook(ReadOnlyPropertyBook propertyBook, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index 9fba0c7a1d6..90f723588c0 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -6,13 +6,14 @@ import seedu.address.commons.exceptions.DataLoadingException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyPropertyBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends AddressBookStorage, PropertyBookStorage, UserPrefsStorage { @Override Optional readUserPrefs() throws DataLoadingException; @@ -29,4 +30,7 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { @Override void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + @Override + void savePropertyBook(ReadOnlyPropertyBook propertyBook) throws IOException; + } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 8b84a9024d5..b7be79d0a86 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -8,6 +8,7 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataLoadingException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyPropertyBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; @@ -18,13 +19,16 @@ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); private AddressBookStorage addressBookStorage; + private PropertyBookStorage propertyBookStorage; private UserPrefsStorage userPrefsStorage; /** * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. */ - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(AddressBookStorage addressBookStorage, + PropertyBookStorage propertyBookStorage, UserPrefsStorage userPrefsStorage) { this.addressBookStorage = addressBookStorage; + this.propertyBookStorage = propertyBookStorage; this.userPrefsStorage = userPrefsStorage; } @@ -46,6 +50,35 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { } + // ================ PropertyBook methods ============================== + + @Override + public Path getPropertyBookFilePath() { + return propertyBookStorage.getPropertyBookFilePath(); + } + + @Override + public Optional readPropertyBook() throws DataLoadingException { + return readPropertyBook(propertyBookStorage.getPropertyBookFilePath()); + } + + @Override + public Optional readPropertyBook(Path filePath) throws DataLoadingException { + logger.fine("Attempting to read data from file: " + filePath); + return propertyBookStorage.readPropertyBook(filePath); + } + + @Override + public void savePropertyBook(ReadOnlyPropertyBook propertyBook) throws IOException { + savePropertyBook(propertyBook, propertyBookStorage.getPropertyBookFilePath()); + } + + @Override + public void savePropertyBook(ReadOnlyPropertyBook propertyBook, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + propertyBookStorage.savePropertyBook(propertyBook, filePath); + } + // ================ AddressBook methods ============================== @Override @@ -74,5 +107,4 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro logger.fine("Attempting to write to data file: " + filePath); addressBookStorage.saveAddressBook(addressBook, filePath); } - } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/CustomerCard.java similarity index 59% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/address/ui/CustomerCard.java index 094c42cda82..cbdc7ed97fb 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/CustomerCard.java @@ -7,14 +7,14 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.person.Person; +import seedu.address.model.customer.Customer; /** - * An UI component that displays information of a {@code Person}. + * An UI component that displays information of a {@code Customer}. */ -public class PersonCard extends UiPart { +public class CustomerCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final String FXML = "CustomerListCard.fxml"; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -24,7 +24,7 @@ public class PersonCard extends UiPart { * @see The issue on AddressBook level 4 */ - public final Person person; + public final Customer customer; @FXML private HBox cardPane; @@ -35,24 +35,24 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML - private Label address; + private Label budget; @FXML private Label email; @FXML private FlowPane tags; /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. + * Creates a {@code CustomerCode} with the given {@code Customer} and index to display. */ - public PersonCard(Person person, int displayedIndex) { + public CustomerCard(Customer customer, int displayedIndex) { super(FXML); - this.person = person; + this.customer = customer; id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() + name.setText(customer.getName().fullName); + phone.setText(customer.getPhone().value); + budget.setText(customer.getBudget().toString()); + email.setText(customer.getEmail().value); + customer.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } diff --git a/src/main/java/seedu/address/ui/CustomerListPanel.java b/src/main/java/seedu/address/ui/CustomerListPanel.java new file mode 100644 index 00000000000..de3f5b3c9a8 --- /dev/null +++ b/src/main/java/seedu/address/ui/CustomerListPanel.java @@ -0,0 +1,49 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.customer.Customer; + +/** + * Panel containing the list of customers. + */ +public class CustomerListPanel extends UiPart { + private static final String FXML = "CustomerListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(CustomerListPanel.class); + + @FXML + private ListView customerListView; + + /** + * Creates a {@code CustomerListPanel} with the given {@code ObservableList}. + */ + public CustomerListPanel(ObservableList customerList) { + super(FXML); + customerListView.setItems(customerList); + customerListView.setCellFactory(listView -> new CustomerListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Customer} using a {@code CustomerCard}. + */ + class CustomerListViewCell extends ListCell { + @Override + protected void updateItem(Customer customer, boolean empty) { + super.updateItem(customer, empty); + + if (empty || customer == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new CustomerCard(customer, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..bcfa3f7c813 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-w11-2.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..f03f6f52252 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -2,6 +2,7 @@ import java.util.logging.Logger; +import javafx.animation.PauseTransition; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; @@ -10,10 +11,12 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; import javafx.stage.Stage; +import javafx.util.Duration; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.logic.Logic; import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; @@ -31,7 +34,8 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private CustomerListPanel customerListPanel; + private PropertyListPanel propertyListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -42,7 +46,10 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane customerListPanelPlaceholder; + + @FXML + private StackPane propertyListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -50,6 +57,9 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane propertyStatusbarPlaceholder; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -110,8 +120,11 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + customerListPanel = new CustomerListPanel(logic.getFilteredCustomerList()); + customerListPanelPlaceholder.getChildren().add(customerListPanel.getRoot()); + + propertyListPanel = new PropertyListPanel(logic.getFilteredPropertyList()); + propertyListPanelPlaceholder.getChildren().add(propertyListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -119,6 +132,9 @@ void fillInnerParts() { StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + PropertyStatusBarFooter propertyStatusBarFooter = new PropertyStatusBarFooter(logic.getPropertyBookFilePath()); + propertyStatusbarPlaceholder.getChildren().add(propertyStatusBarFooter.getRoot()); + CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); } @@ -160,11 +176,17 @@ private void handleExit() { (int) primaryStage.getX(), (int) primaryStage.getY()); logic.setGuiSettings(guiSettings); helpWindow.hide(); - primaryStage.hide(); + resultDisplay.setFeedbackToUser(ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT); + PauseTransition delay = new PauseTransition(Duration.seconds(3)); + delay.setOnFinished(e -> primaryStage.hide()); + delay.play(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + public CustomerListPanel getCustomerListPanel() { + return customerListPanel; + } + public PropertyListPanel getPropertyListPanel() { + return propertyListPanel; } /** diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index f4c501a897b..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,49 +0,0 @@ -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. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - /** - * Creates a {@code PersonListPanel} with the given {@code ObservableList}. - */ - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/PropertyCard.java b/src/main/java/seedu/address/ui/PropertyCard.java new file mode 100644 index 00000000000..d0bb37ab2bf --- /dev/null +++ b/src/main/java/seedu/address/ui/PropertyCard.java @@ -0,0 +1,60 @@ +package seedu.address.ui; + + +import java.util.Comparator; + +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.property.Property; + +/** + * An UI component that displays information of a {@code Property}. + */ +public class PropertyCard extends UiPart { + + private static final String FXML = "PropertyListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Property property; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label phone; + @FXML + private Label address; + @FXML + private Label price; + @FXML + private FlowPane tags; + + /** + * Creates a {@code PropertyCode} with the given {@code Property} and index to display. + */ + public PropertyCard(Property property, int displayedIndex) { + super(FXML); + this.property = property; + id.setText(displayedIndex + ". "); + name.setText(property.getName().fullName); + phone.setText(property.getPhone().value); + address.setText(property.getAddress().value); + price.setText(property.getPrice().toString()); + property.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + } +} diff --git a/src/main/java/seedu/address/ui/PropertyListPanel.java b/src/main/java/seedu/address/ui/PropertyListPanel.java new file mode 100644 index 00000000000..fb06a6ed409 --- /dev/null +++ b/src/main/java/seedu/address/ui/PropertyListPanel.java @@ -0,0 +1,50 @@ +package seedu.address.ui; + + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.property.Property; + +/** + * Panel containing the list of properties. + */ +public class PropertyListPanel extends UiPart { + private static final String FXML = "PropertyListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PropertyListPanel.class); + + @FXML + private ListView propertyListView; + + /** + * Creates a {@code PropertyListPanel} with the given {@code ObservableList}. + */ + public PropertyListPanel(ObservableList propertyList) { + super(FXML); + propertyListView.setItems(propertyList); + propertyListView.setCellFactory(listView -> new PropertyListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Property} using a {@code PropertyCard}. + */ + class PropertyListViewCell extends ListCell { + @Override + protected void updateItem(Property property, boolean empty) { + super.updateItem(property, empty); + + if (empty || property == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PropertyCard(property, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/PropertyStatusBarFooter.java b/src/main/java/seedu/address/ui/PropertyStatusBarFooter.java new file mode 100644 index 00000000000..4859e9d1a97 --- /dev/null +++ b/src/main/java/seedu/address/ui/PropertyStatusBarFooter.java @@ -0,0 +1,28 @@ +package seedu.address.ui; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; + +/** + * A ui for the status bar that is displayed at the footer of the application. + */ +public class PropertyStatusBarFooter extends UiPart { + + private static final String FXML = "PropertyStatusBarFooter.fxml"; + + @FXML + private Label savePropertyLocationStatus; + + /** + * Creates a {@code StatusBarFooter} with the given {@code Path}. + */ + public PropertyStatusBarFooter(Path saveLocation) { + super(FXML); + savePropertyLocationStatus.setText(Paths.get(".").resolve(saveLocation).toString()); + } + +} diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/CustomerListCard.fxml similarity index 94% rename from src/main/resources/view/PersonListCard.fxml rename to src/main/resources/view/CustomerListCard.fxml index f5e812e25e6..177b7df5e0b 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/CustomerListCard.fxml @@ -29,7 +29,7 @@