diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index aabbbdbaf33..48ac24d7fed 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -35,7 +35,16 @@ jobs: java-version: '11' java-package: jdk+fx - - name: Build and check with Gradle + - name: Setup xvfb for headless UI testing + if: runner.os == 'Linux' + run: sudo apt-get install -y xvfb + + - name: Build and check with Gradle (Linux) + if: runner.os == 'Linux' + run: xvfb-run ./gradlew check coverage + + - name: Build and check with Gradle (other OS) + if: ${{ runner.os != 'Linux' }} run: ./gradlew check coverage - uses: codecov/codecov-action@v2 diff --git a/README.md b/README.md index 13f5c77403f..d8c52a03229 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,20 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +# PetCode +[![CI Status](https://github.com/AY2223S1-CS2103T-T09-2/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S1-CS2103T-T09-2/tp/actions) +[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-T09-2/tp/branch/master/graph/badge.svg?token=F6VVPXKC9C)](https://codecov.io/gh/AY2223S1-CS2103T-T09-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)**. -* 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. +## What is PetCode +PetCode is a software app that aims to facilitate better working experience and boost business management efficiency for pet sale coordinators. + +Download [PetCode](https://github.com/AY2223S1-CS2103T-T09-2/tp/releases) to facilitate your business now. + +Visit our [website](https://ay2223s1-cs2103t-t09-2.github.io/tp/) to find out more. + +## Site Map ++ [User Guide](https://ay2223s1-cs2103t-t09-2.github.io/tp/UserGuide.html) ++ [Developer Guide](https://ay2223s1-cs2103t-t09-2.github.io/tp/DeveloperGuide.html) ++ [About us](https://ay2223s1-cs2103t-t09-2.github.io/tp/AboutUs.html) + +## Acknowledgements ++ 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 108397716bd..b2ab73b07f1 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,7 @@ checkstyle { test { useJUnitPlatform() finalizedBy jacocoTestReport + jvmArgs "-Djava.awt.headless=false" } task coverage(type: JacocoReport) { @@ -63,10 +64,15 @@ dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion + } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'petcode.jar' +} + +run { + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..a1d095b91b6 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -5,55 +5,58 @@ title: About Us We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). -You can reach us at the email `seer[at]comp.nus.edu.sg` +You can reach us at the email `seer@comp.nus.edu.sg` -## Project team +## PetCode Team -### John Doe +### Wu Lezheng - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/Wu-Lezheng)] +[[portfolio](team/wu-lezheng.md)] -* Role: Project Advisor +* Role: *UI designer, Developer, Documentation writer, Proofreader, Manual tester, Project manager*. +* Responsibilities: Task management, creation and modification of UI, addition and modification of some features, + proofreading and editing of documents, manual testing and bug reporting. -### Jane Doe +### Zhang Weiqiang - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/wweqg)] +[[portfolio](team/wweqg.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: Integration -### Johnny Doe +### Faith Chua - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/boredcoco)] +[[portfolio](team/boredcoco.md)] -* Role: Developer -* Responsibilities: Data +* Role: *Tester, Developer* +* Responsibilities: *Filter and Find features, testing* -### Jean Doe +### Huang Hongyi - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/Hongyi6328)] +[[portfolio](team/hongyi6328.md)] -* Role: Developer -* Responsibilities: Dev Ops + Threading +* Role: Logic Developer, Intra-structure Developer, Manual Tester, Documentation Writer, Project Manager +* Responsibilities: Issue Manager, Milestone Manager, Official & Unofficial Meeting Minutes Maintainer, Solution + Architect -### James Doe +### Hong Ker Yen Elizabeth - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/elizabethhky)] +[[portfolio](team/elizabethhky.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Delete Feature, Documentation diff --git a/docs/AddSupplierCommandIllustration.png b/docs/AddSupplierCommandIllustration.png new file mode 100644 index 00000000000..6afea5bb735 Binary files /dev/null and b/docs/AddSupplierCommandIllustration.png differ diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..2627d8b7926 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,14 +2,68 @@ layout: page title: Developer Guide --- -* Table of Contents -{:toc} + +
+

PetCode Developer Guide

+Welcome to the PetCode Developer guide!

+ +PetCode is a desktop app that helps store and manage contact information for your pet sales coordination business. +
+ +{:refdef: style="text-align: center;"} +![PetCode Logo](images/PetCode Logo.png) +{: refdef} + +

Table of Contents

+ +- **[Acknowledgements](#acknowledgements)** +- **[Setting up, getting started](#setting-up-getting-started)** +- **[Design](#design)** +- **[Architecture](#architecture)** + * [UI Component](#ui-component) + * [Logic Component](#logic-component) + * [Model Component](#model-component) + * [Storage Component](#storage-component) +- **[Implementation](#implementation)** + * [Unique ID Mechanism](#unique-id-mechanism) + * [Motivation](#motivation-for-unique-id) + * [Implementation](#implementation-of-unique-id) + * [Display of person list](#display-of-person-list) + * [Motivation](#motivation-for-display-of-person-list) + * [Implementation](#implementation-of-display-of-person-list) + * [Alternatives Considered](#alternatives-considered-for-display-of-person-list) + * [Pop-up window for add command](#pop-up-window-for-add-command) + * [Motivation](#motivation-for-pop-up-window) + * [Implementation](#implementation-of-pop-up-window-for-add-command) + * [Alternatives Considered](#alternatives-considered-for-pop-up-window) + * [Match feature](#match-feature) + * [Motivation](#motivation-for-match-feature) + * [Implementation of scoring system](#implementation-of-the-score-system) + * [Sample calculation of the score](#sample-calculation-of-the-score) + * [How the match feature works](#how-the-match-feature-works) + * [Example of how the match feature works](#example-of-how-the-match-command-works) + * [Areas for improvement](#areas-for-improvement-for-match-feature) + * [[Proposed] Undo/Redo feature](#proposed-undoredo-feature) +- **[Documentation, logging, testing, configuration, dev-ops](#documentation-logging-testing-configuration-dev-ops)** +- **[Appendix: Requirements](#appendix-requirements)** + * [Product Scope](#product-scope) + * [User Stories](#user-stories) + * [Use Cases](#use-cases) + * [Non-functional Requirements](#non-functional-requirements) + * [Glossary](#glossary) +- **[Appendix: Instructions for manual testing](#appendix-instructions-for-manual-testing)** + * [Launch and shutdown](#launch-and-shutdown) + * [Delete a buyer](#deleting-a-buyer) + * [Matching a pet to an order](#matching-a-pet-to-an-order) + * [Saving data](#saving-data) +- **[Appendix: Effort](#appendix-effort)** -------------------------------------------------------------------------------------------------------------------- ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +* The features Add, Edit, Delete were reused with minimal changes from the past project + [Address Book Level 3](https://github.com/nus-cs2103-AY2223S1/tp) ([UG](https://github.com/nus-cs2103-AY2223S1/tp/blob/master/docs/UserGuide.md), [DG](https://github.com/nus-cs2103-AY2223S1/tp/blob/master/docs/DeveloperGuide.md)). -------------------------------------------------------------------------------------------------------------------- @@ -23,7 +77,10 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in +the [diagrams](https://github.com/AY2223S1-CS2103T-T09-2/tp/tree/master/docs/diagrams) folder. Refer to the [_PlantUML +Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit +diagrams.
### Architecture @@ -36,7 +93,11 @@ Given below is a quick overview of main components and how they interact with ea **Main components of the architecture** -**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, +**`Main`** has two classes +called [`Main`](https://github.com/AY2223S1-CS2103T-T09-2/tp/blob/master/src/main/java/seedu/address/Main.java) +and [`MainApp`](https://github.com/AY2223S1-CS2103T-T09-2/tp/blob/master/src/main/java/seedu/address/MainApp.java). It +is responsible for, + * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup methods where necessary. @@ -49,19 +110,23 @@ The rest of the App consists of four components. * [**`Model`**](#model-component): Holds the data of the App in memory. * [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. - **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 `delete-b 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. +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. @@ -69,80 +134,190 @@ The sections below give more details of each component. ### UI component -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +The **API** of this component is specified +in [`Ui.java`](https://github.com/AY2223S1-CS2103T-T09-2/tp/blob/master/src/main/java/seedu/address/ui/Ui.java) -![Structure of the UI Component](images/UiClassDiagram.png) +Given below is a partial class diagram of the `Ui` component. -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. + -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The UI consists of a `MainWindow` that is made up of parts including `CommandBox`, `ResultDisplay`, `StatusBarFooter`. +The `MainWindow` also has `HelpWindow` and `AddCommandPopupWindow` that will be shown to the user when required. +Detailed implementation of the `AddCommandPopupWindow` is written [here](#pop-up-window-for-add-command). +All these UI components, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between +classes that represent parts of the visible GUI. + +Furthermore, the `MainWindow` can be filled by **one** list panel, such as `BuyerListPanel` or `PetListPanel`, for display. +The list panel displayed depends on the input `Command`. +Each list panel can have any number of the corresponding card. For example, `BuyerListPanel` can have any number +of `BuyerCard`. +All the list panels and cards inherit from the abstract `UiPart`, but **not shown** in the diagram below to reduce graph +complexity. +Detailed implementation of the list panel can be found [here](#display-of-person-list). + + + +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/AY2223S1-CS2103T-T09-2/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) +is specified +in [`MainWindow.fxml`](https://github.com/AY2223S1-CS2103T-T09-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 `Person` objects residing in the `Model`. ### Logic component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](https://github.com/AY2223S1-CS2103T-T09-2/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: How the `Logic` component works: + 1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to add a person). -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddBuyerCommand`) which is + executed by the `LogicManager`. +1. The command can communicate with the `Model` when it is executed (e.g. to add a buyer). +1. The result of the command execution is encapsulated as a `CommandResult` object which is returned from `Logic`. + +The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete-b 1")` API +call. -The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. +![Interactions Inside the Logic Component for the `delete-b 1` Command](images/DeleteSequenceDiagram.png) -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +
+ +:information_source: **Note:** The lifeline for `DeleteBuyerCommandParser` 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 `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
-Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: +Given below is a diagram showing some other classes in `Logic` (omitted from the class diagram above) that can be 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. +
+ +:information_source: **Note:** Some classes shown such as `ArgumentMultiMap`, `ArgumentTokenizer` and `CliSyntax` may not be used for some `XYZCommandParser` objects. Read the information section below for further explanation. + +
+ +
+ +:information_source: **Different commands have different ways of implementing their respective parsers.**

+ +**Some parsers can return different `Command` objects.**

+The `SortCommandParser` returns one of `SortCommand`'s subclasses -- `SortBuyerCommand`, +`SortDelivererCommand`, `SortSupplierCommand`, `SortOrderCommand`, and `SortPetCommand`.

+The implementation of `SortCommandParser` was done such that the `SortCommand` is able to accept multiple inputs for the +`LIST_TYPE` and `ATTRIBUTES` parameters. Hence, the `SortCommandParser` makes use of `SortCommandParserUtil` +and `CommandUtil` classes which help to parse the multiple valid parameters and return the correct `SortCommand` +subclass.

+Given below is the Parser classes diagram for the `SortCommand`.
+

+ +**Some `Command` objects are similar but have their own parsers and behave distinctly.**

+The `AddressBookParser` creates `DeleteBuyerCommandParser`, `DeleteSupplierCommandParser`, +`DeleteDelivererCommandParser`, `DeleteOrderCommandParser`, or a `DeletePetCommandParser` depending on the user's input. +Each `DeleteCommand` parser then returns the respective `DeleteCommand` to `AddressBookParser` for execution, +i.e `DeleteBuyerCommandParser` parse method returns a `DeleteBuyerCommand` object.

+This way of implementation is done for commands that are very similar but have different `COMMAND_WORD`s, such as the +AddCommand, DeleteCommand, EditCommand, FilterCommand, and FindCommand.

+Given below is the Parser classes diagram for the `DeleteCommand`. +**`ParserUtil` and `Index` classes are omitted from the diagram to reduce graph complexity.**
+ + +
+ +**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., `AddBuyerCommandParser`) which uses the other classes shown above to parse + the user command and create a `XYZCommand` object (e.g., `AddBuyerCommand`) which the `AddressBookParser` returns back as + a `Command` object. +* All `XYZCommandParser` classes (e.g., `AddBuyerCommandParser`, `DeleteBuyerCommandParser`, ...) inherit from the `Parser` + interface so that they can be treated similarly where possible e.g, during testing. ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) - +**API** : [`Model.java`](https://https://github.com/AY2223S1-CS2103T-T09-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 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) +* stores the address book data i.e., all `Buyer`, `Supplier`, `Deliverer`, `Order`, and `Pet` objects (which are contained in + a `UniqueBuyerList`, `UniqueDelivererList`, `UniqueSupplierList`, `UniqueOrderList`, and `UniquePetList` object). +* stores the currently 'selected' `Buyer`, `Supplier`, `Deliverer`, `Order`, and `Pet` objects (e.g., results of a + search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList`, + `ObservableList`, `ObservableList`, `ObservableList`, `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: **How Different Address Book Objects are Stored:**

+The diagrams given below contains more details on how each `Buyer`, `Supplier`, +`Deliverer`, `Order` and `Pet` objects are stored in the Model component.
-
+For more information on what each object represents, refer to the [Glossary](#glossary) section.

+**`Buyer` and `Deliverer` Class**
+ +Both the `Buyer` and `Deliverer` classes inherit from the `Person` class and have an orders attribute. +Each order has an `UniqueId` for easier identification. Hence, the orders are stored as a collection of `UniqueId` +objects to easily access unique orders. Given below is the class diagram for the **`Buyer`** Class:
+ +

+ +**`Supplier` Class**
+ +Similar to the `Buyer` and `Deliverer` class, the `Supplier` class inherits from the `Person` class. However, instead of +an orders attribute, the `Supplier` class has a pets attribute to represent the pets sold by the `Supplier`. +Similar to an order, each pet has an `UniqueId` for easier identification. Hence, the pets are stored as a collection of +`UniqueId` objects to easily access unique pets. Given below is the class diagram for the **`Supplier`** Class:
+ +

+ +**`Order` Class**
+ +The `Order` class consists of several attributes. The most important attribute to take note of is the Buyer as +every order should be made by a Buyer. Given below is the class diagram for the **`Order`** Class:
+ +

+ +**`Pet` Class**
+ +The `Pet` class consists of several attributes. The most important attribute to take note of is the Supplier as +every pet should be sold by a Supplier. Given below is the class diagram for the **`Pet`** Class:
+ + +
+ ### Storage component -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](https://github.com/AY2223S1-CS2103T-T09-2/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) - + The `Storage` component, -* can save both address book data and user preference data in json format, and read them back into corresponding objects. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). -* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) + +* can save both address book data and user preference data in json format, and read them back into corresponding + objects. +* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only + the functionality of only one is needed). +* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects + that belong to the `Model`) ### Common classes @@ -152,31 +327,316 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa ## **Implementation** -This section describes some noteworthy details on how certain features are implemented. +This section describes some noteworthy details on how certain features and functionalities are implemented. + +### Unique ID Mechanism + +#### Motivation for unique ID +The `Buyer` object has a reference to `Order` object(s) and an `Order` object also has reference to a `Buyer` object. +Similarly, the `Supplier` object has a reference to `Pet` object(s) and vice versa. +This bidirectional association makes it difficult to implement some JSON-related classes and methods, +since the JSON-adapted models will recursively write these references into the `.json` file for infinite number of times. + +#### Implementation of unique ID +Our solution to this problem is to give each `Order` and `Pet` a unique ID that does not change throughout the life +cycle of `Order` or `Pet` object. + +We considered using a unique `int` or `long` data type to represent the id, but either `int` or `long` is possible to +have overflow (though very unlikely), resulting in duplicate IDs. Therefore, we thought of another approach, which is +strings. + +We regard a string as a base 26 number (`'a'` - `'z'`). Every time the least significant digit shifts from `'z'` +to `'a'`, we do a carry to the more significant digit. Repeat this step until there is no more carry or the most +significant digit has a carry. In the latter case, we append another `'a'` as the most significant digit. As shown below. + + + + +For efficiency, the ID generator is implemented by a `List` of `char`, which avoids frequent string copying and +concatenating. `List` facilitates fast in-place edit of a single `char` at a single index as well. + +### Display of person list + +#### Motivation for display of person list + +Given below is a partial class diagram of the **old UI**. + + + +Initially, there is only one `PersonListPanel` that displays the person list using `PersonCard`. +However, our product classifies `Person` into three different categories -- `Buyer`, `Supplier`, and `Deliverer`. +Therefore, it is necessary to have a **separate list panel** for each of these three types of `Person`. + +In addition, buyers, suppliers and deliverers have comprehensive information on the orders or pets that they possess (not implemented for deliverers yet), +besides their contact information. +A `PersonCard` with only `Label` of JavaFX will display information in a very unorganised and lengthy way, which is +difficult for users to obtain information quickly. +Therefore, the UI needs to be **optimised for the situation where there is plentiful information** that the user wants +to know about a single `Person`. + +#### Implementation of display of person list + +In the implementation as seen in the diagram below, the `MainWindow` can be filled by any one of the following +depending on the `Command` executed: + +* `BuyerListPanel`: displays information about each `Buyer` using a `BuyerCard` in a `ListView`. +* `SupplierListPanel`: displays information about each `Supplier` using a `SupplierCard` in a `ListView`. +* `DelivererListPanel`: displays information about each `Deliverer` using a `DelivererCard` in a `ListView`. +* `MainListPanel`: displays a master list which includes all `Buyer`, `Supplier`, and `Deliverer` In a `ListView`. +* `OrderListPanel`: displays information about each `Order` using an `OrderCard` in a `ListView`. +* `PetListPanel`: displays information about each `Pet` using a `PetCard` in a `ListView`. + +*Note that each person card (`BuyerCard`, `DelivererCard`, `SupplierCard`) can have any number of the corresponding item +cards (`OrderCard`, `PetCard`).* + + + +By having separate list panels, it will be easier to customise the display of different `Person` types as well +as `Order` and `Pet` if required by future features and ui improvements. + +In each `BuyerCard` as seen in the image below, the buyer's `Name` will be shown together with an index and a label +indicating that (s)he is a `Buyer`. +* The left side of the `BuyerCard` displays the contact information of the `Buyer`, including `Phone`, `Email`, `Location`, and `Address`. +* The right side of the `BuyerCard` is visually enhanced by adding a `ListView` of `OrderCard`, which displays the information of +each `Order` that the `Buyer` has made. Each `Order` is also given an index in the list. + + + +In each `SupplierCard`, the structure is similar to that of the `BuyerCard` except the right side of the card. +Instead of a `ListView` of `OrderCard`, it has a `ListView` of `PetCard` which displays the information of each +`Pet` that the `Supplier` sells. Each `Pet` is also given an index in the list. + +By modifying the `PersonCard` to the three types of cards stated above, divided into a left section which shows contact +details, and a right section which is a `ListView`, we can keep the information displayed organised and maintain the +height of each card within a reasonable range +(e.g. if the orders are displayed as plain text below the buyer's contact information, the card will be stretched +vertically, potentially to an extent that the whole window can only show information of one single buyer). + +#### Alternatives considered for display of person list + +* **Alternative 1 (current choice):** Has only one display window and displays items (`Order` or `Pet`) together with + the person. + * Pros: Easy to implement and can view all the information immediately after a command is executed. + * Cons: Too cramped, which may lead to information overload. +* **Alternative 2:** Has one display window for person and a separate display window for items, as shown below. + * Pros: More organised and visually pleasant. + * Cons: Hard to implement and need one more command such as `display INDEX` to display the information of the person or item. + + + +### Pop-up window for add command + +#### Motivation for pop-up window + +If the user wants to add a `Buyer` with multiple `Order`, or add a `Supplier` with multiple `Pet`, +the user has to repetitively enter a lot of prefixes. +The user also needs to memorise the prefixes for each attribute of the person or item, and they may get lost when entering such a long command. + +Therefore, we recognise the need for a pop-up window for adding a `Person` (`Buyer` or `Supplier` for the current version), +which has text fields that **prompt** the user to enter the required information **without prefixes**. + +#### Implementation of pop-up window for add command + +Given below is the partial class diagram of `Ui` component related to `AddCommandPopupWindow`. + + + +The `AddCommandPopupWindow` is made up of either `PopupPanelForBuyer` or `PopupPanelForSupplier`, depending on the type of `Person` that the user wants to add. +`PopupPanelForBuyer` can have any number of `PopupPanelForOrder`, while `PopupPanelForSupplier` can have any number of `PopupPanelForPet`. +All the pop-up panels inherit from an abstract class `PopupPanel`, which captures the commonalities between classes that represent parts of the content in pop-up window. + +Each subclass of `PopupPanel` can generate a `Command` based on the attributes specified in some classes of the `Model` component. Therefore, it has a dependency on the `Model` component. +The `Command` is then passed to `AddCommandPopupWindow`, which keeps a reference to `Logic` for the execution of the given `Command`, and a reference to `ResultDisplay` for the display of `CommandResult` in the `MainWindow`. + +Given below is the sequence diagram showing how the command line `add supplier` creates the pop-up window step by step. + + + +**How the pop-window for adding a `Supplier` is created:** + +1. Based on the graph above, after the user enters the command line "add supplier", `MainWindow` calls `LogicManager#execute(String)`. +2. The user input is then parsed by `AddressBookParser` and an `AddCommandWithPopup` instance is created. +3. `LogicManager` then executes the `AddCommandWithPopup` and returns the `CommandResult` back to the `MainWindow` +4. The `MainWindow` recognises from the result that a pop-up window is required for adding a `Supplier`, and invokes the `handleAddByPopup` method in itself. +5. The `handleAddByPopup` method then creates a `AddCommandPopupWindow`, which has a `StackPane`. The `StackPane` is in turn filled by a `PopupPanelForSupplier`. +6. The filled `AddCommandPopupWindow` is displayed to the user. + +After the pop-up window is created, the user enters information about the `Supplier` in the provided text fields and saves the inputs. The sequence diagram below illustrates how the pop-up window deals with user inputs on saving step by step. + + + +**How the user's input for a `Supplier` in the pop-window is saved:** +1. The UI detects there is a saving action (either by pressing the save button or using `CTRL + S`). +2. The `AddCommandPopupWindow` calls `PopupPanelForSupplier#checkAllPartsFilled`. If there is at least one compulsory text field without any user input, the pop-up window will do nothing. +3. If all required text fields have user inputs, the `AddCommandPopupWindow` tries to generate a `Command`, during which the `PopupPanelForSupplier` generates a `supplier` using the `generateSupplier()` method in itself. +4. The generation of a `Supplier` invokes the corresponding static methods in the `ParserUtil` class for each of the supplier's attribute, until all inputs are parsed. +5. **(NOT SHOWN IN DIAGRAM)** When there are subcomponents in the `PopupPanelForSupplier` (`PopupPanelForPet` in this context), it also parses the inputs in these subcomponents by calling `PopupPanelForPet#generatePet()` after the `generateSupplier` call. +6. The generated `Supplier` (with / without `Pet`) is used to create an `AddSupplierCommand` instance, which is then returned to the `AddCommandPopupWindow`. +7. The `AddCommandPopupWindow` executes the `AddSupplierCommand` instance, and gets back the `CommandResult`. + +The following activity diagram summarises how the UI responds to an add command with the pop-up window. + + + +
+ +To cater to people who can **type fast**, **keyboard shortcuts** are included in the pop-up window.

+For example, pressing `ESC` closes the pop-up window without saving, and pressing `CTRL + S` saves the user input and closes the pop-up window. +This is achieved using `EventHandler`, `EventFilter` and `KeyCodeCombination` of JavaFX. + +
+ +#### Alternatives considered for pop-up window +* **Alternative 1 (current choice):** Has a separate pop-up window when a `Command` in the form similar to `add supplier` is entered by the user, with multiple text fields that contain prompt text for the user to input. + * Pros: Recognition rather than recall, reducing the user's need to memorise the prefixes required. + * Cons: Hard to implement, less CLI in nature. +* **Alternative 2 (also implemented):** Has a `Command` that can add a `Person` with multiple `Order`/`Pet` by prefixes in the `CommandBox` (single text field, no prompt text) of the `MainWndow`. + * Pros: Easy to implement, more CLI in nature. + * Cons: Tedious when entering the `Command`, a lot of memorisation work to remember the prefixes. + +### Match feature + +#### Motivation for Match feature + +Our target user, pet sales coordinators, needs to find out which pet for sale is the best fit for an order placed by +a buyer. In an `Order`, the buyer can specify attributes such as the age of the pet (s)he wants, the acceptable price interval, and more. We +have intentionally set up the same attributes for a `Pet` object. + +Since there are many attributes the user has to take note of when finding the best fit pet for an order, we have +implemented the `Match` feature which makes comparisons between the attributes of the `Order` object and `Pet` objects to +find the best fit pet. + +#### Implementation of the score system + +We use a score to determine how close a pet matches an order. As shown below, the total score `S` is the sum of `n` +sub-scores. +Every sub-score is the product of an indicator variable `s_i` and a weight `w_i`. Every indicator-weight pair +corresponds to an attribute that both `Pet` and `Order` have. + + + +The indicator variable depends on the attribute it corresponds to. There are two types of indicators: + +1. **Must-have indicators**: They are 1 if the attribute in `Pet` is exactly the same as that in `Order`, otherwise 0. +2. **Deviation indicators**: They are 1 if the attribute in `Pet` is within the expected range of value for the same + attribute in `Order`. How close these indicators are to 1 indicates the deviation the attribute in `Pet` has from the + expected value of the attribute in `Order`, i.e The larger the deviation from 1, the larger the deviation is from the + `Order` expected value. + +We use **must-have indicators** and **high weights** for **must-have attributes**. For example, if the species of the pet is exactly +what the buyer wants, then the must-have indicator is 1 and the weight given is high. This results in a high sub-score +given to the attribute, "pet species". +The **rationale** behind this is that a buyer certainly prioritises the species of the pet (s)he wants, even if other +factors are slightly different from what is expected. + +We use **deviation indicators** and **low weights** for **lower-priority attributes**. For example, if the price of a pet +just falls in the expected price range of an order, then the deviation indicator is 1. Otherwise, the value of the indicator +depends on how far the pet's price is away from the range. + +#### Sample calculation of the score + +| Field | Pet | Requested by Order | Indicator | Weight | Sub-score | +|---------------|-------------|--------------------|------------------|--------|----------------| +| Age | 4 | 5 | 1 - abs(4 - 5) | 30 | 0 * 30 = 0 | +| Color | White | Black | 0 | 100 | 0 * 100 = 0 | +| Color pattern | Dotted | None | 0 | 100 | 0 * 100 = 0 | +| Species | Persian cat | Persian cat | 1 | 500 | 1 * 500 = 500 | +| Price (range) | 50 | 90, 100 | 1 - abs(90 - 50) | 5 | -39 * 5 = -195 | + +In the implementation of our Match feature, the attributes `Color`, `ColorPattern` and `Species` are **must-have +attributes** and thus the indicators for these attributes are **must-have indicators**. The attributes `Age` and `Price` +are **lower-priority attributes** and thus the indicators for these attributes are **deviation indicators**. + +Based on the table above, the total score for this pet is 0 + 0 + 0 + 500 - 195 = **305**. + +#### How the match feature works + +With the scoring system, we calculate the score of all pets against an order and sort these pets in descending order of +their calculated score. This is sorted list of pets is then displayed to the user in the MainWindow. +The pets at the top of the displayed list are likely to be the best fit. + +Given below are the sequence diagrams when `match 1` is executed. + + + +**How the `MatchCommand` is created for `match 1`:** + +1. When `Logic` is called upon to execute the `match 1` command, it uses the `AddressBookParser` class to parse the user input. +2. This results in a `MatchCommandParser` object created to parse the parameter supplied by the user, "1", to create a `MatchCommand`. +3. The `MatchCommand` created is then passed back to the `MatchCommandParser`. +4. The `MatchCommandParser` then passes the created `MatchCommand` back to the `AddressBookParser`. +5. The `AddressBookParser` then passes the created `MatchCommand` back to `LogicManager` for execution. + + + +**How the `MatchCommand` is executed for `match 1`:** +1. **(NOT SHOWN IN DIAGRAM)** The `MatchCommand` gets the `Order` and `Pet` lists and from `Model` and stores the lists as local variables. +2. **(NOT SHOWN IN DIAGRAM)** The `MatchCommand` then uses the `Order` list to retrieve the `Order` at the specified index, which is "1" in this context. +3. A `PetGrader` object is then created to be used for evaluating the scores of each `Pet` in the `Pet` list against the `Order`. +4. A `HashMap` object is then created to be used for storing the score for each `Pet`. +5. Each `Pet` in the `Pet` list is then evaluated by the `PetGrader` object and their scores are stored in the `HashMap` object. +6. A `Comparator` object is created to be used for comparing the scores of the `Pet` objects. +7. **(NOT SHOWN IN DIAGRAM)** The `Comparator` object then compares the scores of the `Pet` objects stored in the `HashMap` object and sorts them. +8. The `MatchCommand` calls on the method `sortPets` in the `Model` using the `Comparator` object created. This sorts the `Pet` list in `Model` according to descending order of the `Pet` calculated scores. +9. The `MatchCommand` then calls on the method `switchToPetList` in `Model` to display the sorted `Pet` list to the user. +10. A `CommandResult` object is then created in the `MatchCommand` and passed back to the `LogicManager`, to display the success message. + +#### Example of how the match command works +To have a deeper understanding of what it does, take a look at the two diagrams below. Take the four pets Shiro, +Ashy, Page and Snowy as examples. Plum and Buddy are ignored for simplicity. + +**We assign a score to each pet according to how many attributes they have are the same as requested, and how much +deviation, if they don’t fit, the attributes have from expected values.** The higher the score, the more suitable the pet. +In this table, Shiro has all requested attributes except its price. However, its price is too far away from the +acceptable range fifty to ninety, so a very low score. Ashy does not satisfy any requirement, so another low score. In +the next row, some of Page’s attributes fit and the others do not, so an intermediate score. Finally, although Snowy is +a little bit old, it satisfies all other requirements. Because the difference between its age and the expected age is +not too big, it has a high overall score. + +![img.png](images/MatchCommandIllustration1.png) + +The next thing our app will do is sort the pets by their scores. This sorted list will be displayed on the screen. Now, +as a smart pet sale coordinator who wants to maximise utility and profit, you may want to sell Snowy to this customer. + +![img.png](images/MatchCommandIllustration2.png) + +#### Areas for improvement for Match feature + +At this stage, the weights are pre-set and fixed, so the score may not truly reflect how important each attribute is from +a buyer's or a sale coordinator's perspective. In future implementations, we will allow users to configure these weights, +if they don't want to use the default weights. ### \[Proposed\] Undo/redo feature #### Proposed Implementation -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +The 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: -* `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. +* `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. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` +and `Model#redoAddressBook()` respectively. Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the +initial address book state, and the `currentStatePointer` pointing to that single address book state. ![UndoRedoState0](images/UndoRedoState0.png) -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 2. The user executes `delete-b 5` command to delete the 5th buyer in the address book. The `delete` command +calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete-b 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) -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +Step 3. The user executes `add-b n/David …​` to add a new person. The `add-b` command also calls `Model#commitAddressBook()` +, causing another modified address book state to be saved into the `addressBookStateList`. ![UndoRedoState2](images/UndoRedoState2.png) @@ -184,7 +644,9 @@ Step 3. The user executes `add n/David …​` to add a new person. The `add` co
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing +the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` +once to the left, pointing it to the previous address book state, and restores the address book to that state. ![UndoRedoState3](images/UndoRedoState3.png) @@ -201,17 +663,23 @@ The following sequence diagram shows how the undo operation works: -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once +to the right, pointing to the previously undone state, and restores the address book to that state.
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5. The user then decides to execute the command `list buyer`. Commands that do not modify the address book, such +as `list buyer`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. +Thus, the `addressBookStateList` remains unchanged. ![UndoRedoState4](images/UndoRedoState4.png) -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not +pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be +purged. Reason: It no longer makes sense to redo the `add-b n/David …​` command. This is the behavior that most modern +desktop applications follow. ![UndoRedoState5](images/UndoRedoState5.png) @@ -234,11 +702,6 @@ The following activity diagram summarizes what happens when a user executes a ne _{more aspects and alternatives to be added}_ -### \[Proposed\] Data archiving - -_{Explain here how the data archiving feature will be implemented}_ - - -------------------------------------------------------------------------------------------------------------------- ## **Documentation, logging, testing, configuration, dev-ops** @@ -257,71 +720,238 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +Coordinators of pet sale who need a contact list of both clients, deliverers and suppliers. These coordinators run their +business online and get used to typing. Such people need to maintain a contact list of clients, deliverers, and +suppliers. + +* get used to desktop for their online business, and can type fast +* meet a lot of people online +* need to contact a lot of people on a regular basis +* need to keep track of fast-growing pets +* need to find suppliers for customer demands +* need to find customers for suppliers' pets +* need to do demand-supply matching +* need to arrange international deliveries -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: + +* It is difficult to coordinate (international) pet sales. Suppliers have pets for sale, and clients may have a rough + idea about what pets they want to buy. Once the need and the supply match, deliverers have to carry out the deal. Such + need-supply matching and international pet shipment is difficult to manage. Our app will serve as a more convenient + tool for pet sale coordinators to manage the whole process. Our app will record the needs of clients, current unsold + pets from suppliers, and deliverers’ details. It will automatically match the best-fit pet to a client’s needs. +* Coordinators who run their business online need delivery. Given the location (country) of the client and the supplier, + our app will generate a list of deliverers who have a service over the line, based on records. +* Unlike other products, pets need a certificate to be legally sold - including photos of the animals, whether they are + pure-bred etc. Our app will also help manage certificates. ### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | - -*{More to be added}* +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|---------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|-----------------------------------------------------------------| +| `* * *` | pet sale coordinator who has new partnerships | add contacts of buyers, deliverers and suppliers | keep in touch with them whenever needed. | +| `* * *` | pet sale coordinator who types fast | add contacts of buyers, deliverers and suppliers using CLI | efficiently manage my business. | +| `* *` | pet sale coordinator who is more familiar with GUI | add contacts of buyers, deliverers and suppliers using GUI | not be confused by CLI commands and prefixes. | +| `* * *` | pet sale coordinator | be able to delete any contacts of people | remove entries that I no longer need. | +| `* *` | pet sale coordinator whose clients change their contacts frequently | be able to edit contact information about clients | get their latest number and address for easy contact. | +| `* *` | pet sale coordinator | be able to find all contacts (buyers, suppliers, deliverers) by attributes (e.g. email) | not waste time searching for a specific contact details. | +| `* * *` | pet sale coordinator | list a summary of all contacts in storage | have an overview of my network. | +| `* *` | pet sale coordinator who has business partners from different backgrounds | sort all contacts by attributes (e.g. name) | have a more organised view of my business partners. | +| `* * *` | pet sale coordinator | add an order to a buyer | keep track of they want to buy and what their requirements are. | +| `* * *` | organised pet sale coordinator | delete an order from a buyer | free up storage and clean workplace. | +| `* * *` | pet sale coordinator | list a summary of all orders from the buyers in storage | have an overview of what the buyers want. | +| `* *` | pet sale coordinator | be able to filter all orders by attributes (e.g. price range) | not waste time searching for a specific order. | +| `* *` | pet sale coordinator | check which buyer is associated with an order | see the contact detail of the buyer to negotiate. | +| `* *` | pet sale coordinator with many orders to handle | sort the orders from the buyers based on their urgency (time) | know which order I should deal with first. | +| `* * *` | pet sale coordinator | add a pet to a supplier | keep track of what they want to sell and their stock situation. | +| `* * *` | organised pet sale coordinator | delete a pet from a supplier | free up storage and clean workplace. | +| `* * *` | pet sale coordinator | list a summary of all pets from the suppliers in storage | have an overview of what the suppliers have. | +| `* * *` | pet sale coordinator | be able to filter all pets by attributes (e.g. color) | not waste time searching for a specific pet. | +| `* *` | pet sale coordinator | check which supplier is associated with a pet | see the contact detail of the supplier to negotiate. | +| `* * *` | pet sale coordinator with many pets | find the pet that best matches a specific order | efficiently find the best pet for my client. | +| `* * *` | pet sale coordinator with many pets available for sale | sort the pets based on relevance (e.g. price) | know which pet is the most relevant to my concern | +| `*` | beginner user | read guides and seek help | know how to use PetCode | +| `*` | normal user | quit when I do not need it for now | save my computer resources | +| `*` | pet sale coordinator with messy records of contacts and items | clear all records | start from the beginning to organise my data | ### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +(For all use cases below, the **System** is the `PetCode` and the **Actor** is the `User`, unless specified otherwise) -**Use case: Delete a person** +**Use case: UC01 - Listing contacts / items** **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User specifies which list to show. +2. PetCode displays the specified list. - Use case ends. +Use case ends. **Extensions** -* 2a. The list is empty. +1a. PetCode detects that the list being specified by the User does not exist.
+     1a1. PetCode notifies User that the list does not exist.
+     1a2. User specifies new list.
+     Steps 1a1-1a2 are repeated until the list exists.
+     Use case resumes from step 2. + +Use case ends. - Use case ends. +**Use case: UC02 - Adding an Order from a Buyer** -* 3a. The given index is invalid. +**MSS** - * 3a1. AddressBook shows an error message. +1. User specifies who the buyer is and keys in the buyer's order details. +2. PetCode saves this order. +3. PetCode displays the updated buyer list with the specified buyer having a new order. - Use case resumes at step 2. +Use case ends. -*{More to be added}* +**Extensions** -### Non-Functional Requirements +1a. PetCode detects that the specified buyer does not exist.
+     1a1. PetCode notifies the User that the buyer does not exist.
+     1a2. User specifies new buyer.
+     Steps 1a1-1a2 are repeated until the buyer exists.
+     Use case resumes from step 2.
-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. +1b. PetCode detects that there are missing / invalid order details.
+     1b1. PetCode notifies the User that there are missing / invalid order details.
+     1b2. User specifies keys in new order details.
+     Steps 1b1-1b2 are repeated until the order details are valid.
+     Use case resumes from step 2.
-*{More to be added}* +**Use case: UC03 - Deleting a contact / item** + +**MSS** + +1. User specifies the type of contact / item and the index of the contact / item they want to delete. +2. PetCode searches for this contact / item. +3. PetCode removes this contact / item from the list. +4. PetCode notifies user that contact / item has been deleted from the list and displays updated contact / item list. + +Use case ends. + +**Extensions** + +2a. PetCode detects that the specified contact / item does not exist.
+     2a1. PetCode notifies the User that the contact / item does not exist.
+     2a2. User specifies new contact / item.
+     Steps 2a1-2a2 are repeated until the contact / item exists.
+     Use case resumes from step 3.
+ +Use case ends. + +**Use case: UC04 - Finding a contact based on an attribute** + +**MSS** + +1. User specifies whether (s)he is searching for a Buyer, Supplier or Deliverer, or any kind of contact and the target attribute. +3. PetCode searches for all Buyers, Suppliers or Deliverers with that specified target attribute. +4. PetCode displays these Buyers, Suppliers, Deliverers or all three. + +Use case ends. + +**Extensions** + +1a. User inputs an invalid contact type / missing the contact type.
+     1a1. PetCode notifies the User that the contact type is invalid / missing.
+     1a2. User specifies new contact type.
+     Steps 1a1-1a2 are repeated until the contact type is valid.
+     Use case resumes from step 2.
+ +1b. User inputs an invalid / missing target attribute.
+     1b1. PetCode notifies the User that the target attribute is invalid / missing.
+     1b2. User specifies new target attribute.
+     Steps 1b1-1b2 are repeated until the target attribute is valid.
+     Use case resumes from step 2.
+ +**Use case: UC05 - Sorting contacts / items** + +**MSS** + +1. User specifies the contacts / items list to sort and the attribute(s) to sort by. +2. PetCode sorts the specified list in descending order according to the specified attribute(s). +3. PetCode displays the sorted list to the User. + +Use case ends. + +**Extensions** + +1a. PetCode detects that the specified list is invalid.
+     1a1. PetCode notifies User that the list specified is invalid. +     1a2. User specifies new list type.
+     Steps 1a1-1a2 are repeated until the list type is valid.
+     Use case resumes from step 2.
+ +1b. PetCode detects that the specified attribute(s) is / are invalid.
+     1a1. PetCode notifies User that the specified attribute(s) is / are invalid. +     1a2. User specifies new attribute(s).
+     Steps 1a1-1a2 are repeated until the attribute(s) is / are valid.
+     Use case resumes from step 2.
+ +**Use case: UC06 - Filtering items** + +**MSS** + +1. User specifies the type of items list to filter and keys in the target attribute(s) (s)he is searching for in an item. +2. PetCode searches for all specified items with target attribute(s), depending on what the user has specified. +3. PetCode displays all items that match the target attribute(s). + +Use case ends. + +**Extensions** + +1a. PetCode detects that the specified list is invalid.
+     1a1. PetCode notifies User that the list specified is invalid. +     1a2. User specifies new list type.
+     Steps 1a1-1a2 are repeated until the list type is valid.
+     Use case resumes from step 2.
+ +1b. PetCode detects that the specified attribute(s) is / are invalid.
+     1a1. PetCode notifies User that the specified attribute(s) is / are invalid. +     1a2. User specifies new attribute(s).
+     Steps 1a1-1a2 are repeated until the attribute(s) is / are valid.
+     Use case resumes from step 2.
+ +**Use case: UC07 - Checking the buyer of an order** + +**MSS** + +1. User specifies the order list and the index of the order to be checked. +2. PetCode searches for the order at the specified index. +3. PetCode searches for the buyer of that specified order. +4. PetCode outputs the buyer of that order. + +Use case ends. + +**Extensions** + +1a. The index is not a valid index.
+     1a1. PetCode notifies user that the index is invalid.
+     1a2. User specifies new index.
+     Steps 1a1-1a2 are repeated until the index is valid.
+     Use case resumes from step 2.
+ +### Non-Functional Requirements + +1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. +2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be + able to accomplish most of the tasks faster using commands than using the mouse. +4. The user interface should be intuitive enough for users who are not IT-savvy. ### Glossary +* **Buyer**: A customer of the pet sale coordinator interested in purchasing a pet. +* **Deliverer**: A person that is able to provide delivery services from the supplier to buyer/client. +* **Supplier**: A person that has pets on sale. +* **Item**: An order or a pet. +* **Contact / Person**: A buyer/client, or a deliverer, or a supplier. * **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others -------------------------------------------------------------------------------------------------------------------- @@ -329,7 +959,9 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli Given below are instructions to test the app manually. -
:information_source: **Note:** These instructions only provide a starting point for testers to work on; +
+ +:information_source: **Note:** These instructions only provide a starting point for testers to work on; testers are expected to do more *exploratory* testing.
@@ -338,40 +970,89 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - 1. Download the jar file and copy into an empty folder + 1. Download the jar file and copy into an empty folder - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 2. Double-click the jar file. + * Expected: Shows the GUI with a set of sample contacts. The window size may not be + optimum. 1. Saving window preferences - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 1. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
+ 1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ +### Deleting a buyer -### Deleting a person +1. Deleting a buyer while all buyers are being shown -1. Deleting a person while all persons are being shown + 1. Prerequisites: List all buyers using the `list buyer` command. Multiple buyers in the list. - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 2. Test case: `delete-b 1`
+ Expected: First buyer is deleted from the list. Details of the deleted contact shown in the message in the result display box. - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + 3. Test case: `delete-b 0`
+ Expected: No person is deleted. Error details shown in the message in the result display box. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + 4. Other incorrect delete commands to try: `delete`, `delete-b x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. +### Matching a pet to an order -1. _{ more test cases …​ }_ +1. Matching a pet with the exact same attributes as the order's requests + 1. Change the display list to be the buyer list by using `list buyer`. + 2. Add a new order by using this command `add-o 1 o_st/Pending o_r/add-r o_a/1 o_sp/Shiba Inu o_c/White o_cp/None o_pr/600, 1000 o_d/2022-10-20`. + 3. Change the display list to be the supplier list by using `list supplier`. + 4. Add a new pet by using this command `add-p 1 p_n/Kuro p_d/2021-11-10 p_c/White p_cp/None p_h/50 p_w/20 p_s/Shiba Inu p_v/true p_p/700`. + 5. Add another pet using this command `add-p 1 p_n/Aki p_d/2021-11-10 p_c/Black p_cp/None p_h/50 p_w/20 p_s/Shiba Inu p_v/true p_p/700`. + 6. Use the list command `list order` to view all orders and get the index of the order you have just added, it should be at the bottom of the order list. + 7. Next, use `match INDEX_OF_ORDER`, where the `INDEX_OF_ORDER` is the index of the order you have just added. + Expected: The display list should show that `Kuro` at the top of the pets list and `Aki` ranked below `Kuro`. ### Saving data 1. Dealing with missing/corrupted data files - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ - -1. _{ more test cases …​ }_ + 1. Delete the `addressbook.json` file to simulate missing date file. Launch the application.
+ Expected: A new `addressbook.json` file is created with some sample data. + +2. Dealing with invalid data in data file + + 1. Open the `addressbok.json` file. Change one of the fields to an invalid data, e.g change one of the `personCategory` + fields under the `buyers` to be `invalid`. Launch the application.
+ Expected: Application starts up with no sample data. + +## **Appendix: Effort** +Our group feels that we have put in **much more effort** than what was required for this project. We faced several difficulties +in maintaining several entity types. Due to the nature of our product, we had to handle five different entities - `Buyer`, +`Supplier`, `Deliverer`, `Order` and `Pet`. Each entity also contains several attributes which makes it even more difficult +to manage. Moreover, we had to account for how these entities and their attributes are displayed to the user in the `MainWindow` of our application. +This led to a lot of issues with the UI as we had to ensure that the logic of each command and the information displayed to the users +are coherent. + +Due to the existence of five entities, we also had to extend the commands for each of these entities. For instance, we could no +longer have just one `AddPersonCommand` but had to extend the add command to be `AddBuyerCommand`, `AddSupplierCommand`, +`AddDelivererCommand`, `AddOrderCommand` and `AddPetCommand`. The same goes for several other commands such as the `DeleteCommand`, +`EditCommand`, `ListCommand` etc. On top of the fundamental commands, we have also implemented a few other commands such as the +`MatchCommand`, `SortCommand` and `FilterCommand` to address the needs of our target user. + +Furthermore, since we wanted a smooth user experience for our product, we came up with a `AddCommandWithPopup` feature +where users can add a `Buyer` and `Supplier` contact along with their respective `Order` and `Pet` using a popup window +instead of the command box. This is to reduce the user's frustration in handling multiple prefixes when adding these contacts. +However, to make it appeal more to CLI users, we also implemented shortcut keys in this feature so that users can simply +use their keyboard to fill in contact details and item details. + +The addition of all these classes demanded a lot of refactoring to the code base of AB3. For instance, we had to update +the `Storage` component to store all of these entities in a json-readable format. Furthermore, we had to design many testcases +to ensure that our code was working how we wanted it to. All of these refactoring took a lot of time, effort and teamwork to accomplish. + +Lastly, we spent a lot of time updating and proofreading the User Guide and Developer Guide to ensure that our users and +future developers would understand how our product works. There was a lot of changes made to the User Guide and Developer +Guide due to the addition of many new entities and commands. + +Overall, as our team is dedicated to making our product address the needs of our users and improving the user experience +when using our product, we have spent **a lot of time and effort** on this project. We have built upon the original AB3 +code base by leaps and bounds (as evident by the number of lines of code written by our team ~30 kLoC). We sincerely wish +that our users would find our product addresses their needs and that future developers would be interested in contributing +to our product. diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 275445bd551..6f4668b2e5e 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -3,8 +3,9 @@ layout: page title: Setting up and getting started --- -* Table of Contents -{:toc} +## Table of Contents +* [Setting up the project in your computer](#setting-up-the-project-in-your-computer) +* [Before writing code](#before-writing-code) -------------------------------------------------------------------------------------------------------------------- @@ -45,7 +46,7 @@ If you plan to use Intellij IDEA (highly recommended): 1. **Learn the design** - When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture). + When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [PetCode’s architecture](DeveloperGuide.md#architecture). 1. **Do the tutorials** These tutorials will help you get acquainted with the codebase. diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..95954f41854 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -3,8 +3,9 @@ layout: page title: Testing guide --- -* Table of Contents -{:toc} +## Table of Contents +* [Running tests](#running-tests) +* [Types of tests](#types-of-tests) -------------------------------------------------------------------------------------------------------------------- diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..7bf41d2e3ad 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -2,191 +2,1047 @@ layout: page title: User Guide --- +
+

PetCode User Guide

+Welcome to the PetCode 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. +PetCode is a desktop app that helps store and manage contact information for your pet sales coordination business. +
-* Table of Contents -{:toc} +{:refdef: style="text-align: center;"} +![PetCode Logo](images/PetCode Logo.png) +{: refdef} + +#### Using this guide +If this is the first time you are using this user guide, it is highly recommended for you to read the section on +[Introducing PetCode](#introducing-petcode). Otherwise, + +* If you are setting up, please take a look at our [Quick Start guide](#quick-start). +* If you are unsure of how to use PetCode, the [Command Summary](#command-summary) table is a good starting point. +* If you are a developer and want to help out, please take a look at the [Developer Guide](DeveloperGuide.md). +* For quick navigation in this guide, click the hyperlink at the end of each command to go back to the table of contents, and navigate to other sections of this guide from there. + +## Table of Contents +- **[Introducing PetCode](#introducing-petcode)** + * [What is PetCode?](#what-is-petcode) + * [Glossary](#glossary) + * [How to interpret the display window](#how-to-interpret-the-display-window) +- **[Quick Start](#quick-start)** +- **[Commands](#commands)** + * [Viewing help](#viewing-help--help) + * [Listing contacts or items](#listing-contacts-or-items--list) + * [Checking which item belongs to which contact](#checking-which-item-belongs-to-which-contact--check) + * [Adding a contact or item](#adding-a-contact-or-item--add) + + [Adding a buyer](#adding-a-buyer--add-b) + + [Adding a deliverer](#adding-a-deliverer--add-d) + + [Adding a supplier](#adding-a-supplier--add-s) + + [Adding an order to a buyer](#adding-an-order-to-a-buyer--add-o) + + [Adding a pet to a supplier](#adding-a-pet-to-a-supplier--add-p) + + [Adding a contact with a popup window](#adding-a-contact-with-a-popup-window--add) + * [Matching pets to an order](#matching-pets-to-an-order--match) + * [Deleting a contact or item](#deleting-a-contact-or-item--delete) + * [Editing attributes of a contact](#editing-attributes-of-a-contact--edit) + * [Finding contact(s) using keywords](#finding-contacts-using-keywords--find) + + [Finding a buyer](#finding-a-buyer--find-b) + + [Finding a supplier](#finding-a-supplier--find-s) + + [Finding a deliverer](#finding-a-deliverer--find-d) + * [Filtering items by attributes](#filtering-items-by-attributes--filter) + + [Filtering orders](#filtering-orders--filter-o) + + [Filtering pets](#filtering-pets--filter-p) + * [Sorting contacts](#sorting-contacts-and-items--sort) + * [Clearing all contacts](#clearing-all-entries--clear) + * [Exiting the program](#exiting-the-program--exit) + * [Automation of information flow (future feature)](#automation-of-information-flow-coming-in-v20) +- **[How data is stored](#how-data-is-stored)** + * [Saving contacts and items](#saving-the-data) + * [Editing the data file](#editing-the-data-file) + * [Archiving data files](#archiving-data-files-coming-in-v20) +- **[FAQ](#faq)** +- **[Summaries](#summaries)** + * [List of prefixes](#list-of-prefixes) + * [Command summary](#command-summary) -------------------------------------------------------------------------------------------------------------------- +## Introducing PetCode + +Whether you're new to PetCode, or just want to learn more about the details -- this section has you covered. +This section will provide an overview of PetCode and explain key terms. + +### What is PetCode? + +PetCode is a free, open-source application designed for pet sales coordinators for contact information management. + +Due to the nature of a pet sales coordination business, you most likely have **a lot of information you need to deal with**. +For example, what orders have you received? Which orders have not been fulfilled? How should you match this order with +the pets available? What is the contact information of your pet buyers, pet suppliers and delivery services? + +PetCode is designed specifically to **improve your workflow**, by managing all this information to efficiently close +deals and satisfy your customers. It can be used to offload information, categorise them more meaningfully, and match +your customers' requests to their dream pet. + +### How to interpret the display window + +When you first run the app, you may see a display window pop up similar to the one below. We call this window the **Main Window**. +![Starting Display Window](images/StartUIPage.png) + +The following diagram below shows how you should interpret this display window. +* The **Command Box** refers to the text field where you can type commands in. +* The **Display List for Contacts / Items** refers to the list of contacts / items you are currently displaying. + You may enter the following commands in the Command Box to see how the Display List changes: + * `list buyer` lists all buyers. + * `delete-b 1` deletes the buyer with index 1. + * `list order` lists all orders. + ![Interpreted Display Window](images/InterpretGUI.png) + +### Glossary + +In the user guide, you may come across some terms you do not understand. The following table provides clarification +of the terms commonly used in PetCode. + +| Term | Description | +|:----------------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Alphanumeric** | Digits and letters only. For example, `AB3`, `PetCode`, `coco123`, and `2103` are alphanumeric. `#01-04`, `email@domain.com`, and `white spaces` are not. | +| **Attribute** | Words that follow prefixes to describe properties, states, characteristics, and traits. Examples are price, weight, name, and order status. | +| **Command** | A command is a specific instruction you can give to PetCode to perform an action. You can view the list of commands available [here](#command-summary). | +| **Contact** | A contact is an information entry in PetCode. There are three types of contacts you can add - `Buyer`, `Supplier` and `Deliverer`. You can add a contact with the [`add` command](#adding-a-contact-or-item-add). | +| **CLI** | Command-Line Interface (CLI) receives commands from a user in the form of lines of text. It refers to the input textbox in this context. | +| **GUI** | GUI stands for Graphical User Interface. It refers to the display window of the PetCode application. | +| **Index** | The index of the contact or item in the display list for contacts/items. | +| **Integer** | Whole number | +| **Item** | An item refers to an `Order` or a `Pet`. An Order refers to the order placed by a buyer. A Pet refers to the pet available for sale. | +| **Parameter** | A parameter refers to the information you need to give to your command such that it can execute an action based on that information.
For example, the [`list` command](#listing-contacts-or-items--list) requires a KEY parameter to know what kind of list to display. `list buyer` displays your list of buyers, where the KEY parameter is `buyer`. | +| **Prefix** | A prefix indicates the kind of information you are keying in. You can view the list of prefixes available [here](#list-of-prefixes). | +| **Whitespace** | An empty character, or a placeholder character ` `. | + ## Quick start -1. Ensure you have Java `11` or above installed in your Computer. +1. Ensure you have Java `11` or above installed in your Computer. Please kindly refer + [here](https://blog.hubspot.com/website/check-java-verison) for further instructions on how to do so. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +2. Download the latest `petcode.jar` from [here](https://github.com/AY2223S1-CS2103T-T09-2/tp/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +3. Copy the file to the folder you want to use as the _home folder_ for your PetCode app. -1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+4. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note that the app + contains some sample data and the pet images are unchangeable in the current version of PetCode. Clicking the hyperlinks under the pet image will just open your browser and does nothing more.
![Ui](images/Ui.png) -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+5. Type the command in the command box and press ENTER to execute it. e.g. typing **`help`** and pressing ENTER will + open the help window.
Some example commands you can try: - * **`list`** : Lists all contacts. + * **`list buyer`** : Lists all buyers. - * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. + * **`add-b n/Hongyi ph/11223344 e/email@u.nus.edu a/UTR 138600 l/Singapore`** : Adds + a pet buyer named `Hongyi` to the PetCode. - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. + * **`delete-b 1`** : Deletes the first contact from the buyer contacts list. - * **`clear`** : Deletes all contacts. + * **`clear`** : Deletes all contacts. You can use this command to clear all the sample data provided. Be extra careful when using this command since **there is no `undo`**. - * **`exit`** : Exits the app. + * **`exit`** : Exits the app. -1. Refer to the [Features](#features) below for details of each command. +6. Refer to the [Commands](#commands) Section below for details of each command. + +[Go back to [Table of Contents](#table-of-contents)] -------------------------------------------------------------------------------------------------------------------- -## Features +## Commands
-**:information_source: Notes about the command format:**
+:information_source: **How to interpret the Command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
+* Words in `UPPER_CASE` are the parameters to be supplied by you.
e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. * Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. + e.g. `p_n/PET_NAME [p_cert/CERTIFICATE]` can be used as `p_n/Page p_cert/USA Bureau of Exportation Certified` or as `p_n/Page`. * 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. - -* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. + e.g. `[p_cert/CERTIFICATE]…​` can be used as ` ` (i.e. 0 times), `p_cert/noble blood`, `p_cert/noble blood p_cert/house-trained` etc. -* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. +* If a parameter is expected only once in the command, but you specified it multiple times, only the last occurrence of + the parameter will be taken.
+ e.g. if you specify `ph/12341234 ph/56785678`, only `ph/56785678` will be taken. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
+* Extraneous parameters for commands that do not take in parameters (such as `help`, `exit` and `clear`) will be + ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`. +* If the command format contains round brackets `()`, it means the command format inside `()` is omitted and can be found in another command.
+ e.g. in `add-b n/NAME ph/PHONE_NUMBER e/EMAIL a/ADDRESS l/LOCATION o/add-o(order1 prefixes and parameters)…​`, the detailed command format inside `()` is omitted and can be found in [Adding an order to a buyer](#adding-an-order-to-a-buyer--add-o). + +* Unless otherwise specified, the order of prefixes does not matter.
+ e.g. if the command specifies `n/NAME ph/PHONE_NUMBER`, `ph/PHONE_NUMBER n/NAME` is also acceptable, unless stated otherwise in a particular command. + +
+[Go back to [Table of Contents](#table-of-contents)] + ### Viewing help : `help` -Shows a message explaning how to access the help page. +Shows a message explaining how to access the help page. -![help message](images/helpMessage.png) +![help message](images/helpWindow.png) Format: `help` +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +### Listing contacts or items : `list` + +Displays the specified type of contacts or items. This command is especially useful when you want to view all contacts or items of the same type, +or when you want to set the display list to the correct list as required by other commands. + +Format: `list KEY` + +#### KEY Table + +| Contact / Item to List | KEY | +|:----------------------:|:------------:| +| Buyer | buyer, b | +| Supplier | supplier, s | +| Deliverer | deliverer, d | +| Order | order, o | +| Pet | pet, p | +| All Person | all, a | + + +Examples: +* `list buyer` or `list b`, lists all Buyer contacts with their orders. +* `list deliverer` or `list d`, lists all Deliverer contacts. +* `list supplier` or `list s`, lists all Supplier contacts with their pets. +* `list all` or `list a`, lists all Buyer, Deliverer, Supplier contacts and their respective pets and orders details. +* `list order` or `list o`, lists all Orders. +* `list pet` or `list p`, lists all Pets. + +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +### Checking which item belongs to which contact : `check` + +Checks a contact at the specified index and shows his/her items, or checks an item at the specified index and shows the contact it belongs to. +This command is especially useful when you want to switch the display between related buyers and orders, or switch the display between related suppliers and pets. + +Format: `check KEY INDEX` + +#### KEY Table + +| Contact / Item to Check | KEY | +|:-----------------------:|:------------:| +| Buyer | buyer, b | +| Supplier | supplier, s | +| Order | order, o | +| Pet | pet, p | + +#### Expected Behaviour for each type of the Check command + +| Examples | Expected behaviour | +|-----------------|-------------------------------------------------------------------------------------------| +| `check buyer 1` | Shows the list of orders from the buyer at index 1, if at index 1 is a buyer. | +| `check s 2` | Shows the list of pets on sale from the supplier at index 2, if at index 2 is a supplier. | +| `check order 3` | Shows the buyer of the order at index 3, if at index 3 is an order. | +| `check p 4` | Shows the supplier of the pet at index 4, if at index 4 is a pet. | + +
+ +:exclamation: **Caution:** For the current version of PetCode, `check deliverer INDEX` will just display an empty list, since adding orders to deliverers is not yet implemented. +In the future, you may be able to transfer orders from buyers to deliverers and check out the deliverers' orders. + +
+ +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +### Adding a contact or item : `add` + +Adds a contact or item to the address book. + +* A contact can be of three categories: Buyer, Deliverer, and Supplier. +* An item can be either an Order or Pet. + +General Format for add command: `add-KEY prefix/PARAMETERS…​`, where: + +* `KEY` specifies what type of contact or item you want to add. +* `prefix` indicates the kind of information you are adding. +* `PARAMETER` is the content of the information you are adding. + +Kindly refer to the [Summaries](#summaries) section for more information on the available prefixes and a summarised list +of add commands. + +
+ +:bulb: **Tip:** If you are a beginner, we highly recommend you to use +the [Add Command using the popup window](#adding-a-contact-with-a-popup-window--add) +instead of the usual CLI interface. + +
+ +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +#### Adding a buyer : `add-b` + +Adds a buyer to the contact list. A buyer is a person who would like to buy pet(s) and places one or more orders describing what kind +of pet(s) he/she would like. + +You can add a buyer without orders as shown below. + +Format: `add-b n/NAME ph/PHONE_NUMBER e/EMAIL a/ADDRESS l/LOCATION` + +Example: +* To add a single buyer: `add-b n/Hongyi ph/11223344 e/email@u.nus.edu a/UTR 138600 l/Singapore` + +
+ +:information_source: **What is the difference between address and location?**
+ + * **Address** is the specific street number and unit number of the place.
+ * **Location** is the country this person is based.
+ +Since PetCode caters to international pet sale, it is good to have location as a separate attribute. +Different countries have different regulations on pet sale, and you may need to filter persons by their locations for some reason. + +
+ +What if the buyer that you want to add already has some orders? +**You can add a buyer and his/her orders in one shot!** Check it out below :point_down:

+Format: `add-b n/NAME ph/PHONE_NUMBER e/EMAIL a/ADDRESS l/LOCATION o/add-o(order1 prefixes and parameters) o/add-o(order2 prefixes and parameters)…​` + +
+ +:bulb: **Tip:** You can input as many `o/add-o` prefixes as you need. Each `o/add-o` creates an order under the buyer. +After each `o/add-o`, simply enter the details of the order without specifying the index of the associated buyer. +Don't know how to add an order properly? Refer to [Add Order](#adding-an-order-to-a-buyer--add-o) for more information. + +
+ +
+ +:bulb: **Tip:** For more details on what each prefix represents, kindly refer to [List of Prefixes](#list-of-prefixes). + +
+ +Examples: +* To add a buyer with one + order: `add-b n/Hongyi ph/11223344 e/hhygg@u.nus.edu a/UTR 138600 l/Singapore o/add-o o_st/Pending o_r/add-r o_a/1 o_sp/Siamese cat o_c/black o_cp/black and brown o_p/30 o_pr/20, 50 o_d/2022-10-26 o_ar/vaccinated o_ar/free delivery` +* To add a buyer with two + orders: `add-b n/Hongyi ph/11223344 e/hhygg@u.nus.edu a/UTR 138600 l/Singapore o/add-o o_st/Pending o_r/add-r o_a/1 o_sp/Siamese cat o_c/black o_cp/black and brown o_p/30 o_pr/20, 50 o_d/2022-10-26 o_ar/vaccinated o_ar/free delivery o/add-o o_st/Negotiating o_r/add-r o_a/3 o_sp/Shih Tzu o_c/white o_cp/dotted white o_p/44.1 o_pr/10.6, -1 o_d/2022-09-20 o_ar/noble blood o_ar/not naughty` + + +
+ +:exclamation: **Caution**: Take note that the above added buyers are considered as the SAME, so if you try all these sample commands, PetCode will notify you that the buyer already exists in the list. +Check out [FAQ](#faq) on the concept of "How contacts and items are considered as duplicates". + +
+ + +
+ +:bulb: **Tip:** **Overwhelmed by the prefixes** when adding multiple orders? +Check out [Add Command using the popup window](#adding-a-contact-with-a-popup-window--add) to add multiple orders when adding a buyer **without prefixes**. + +
+ +To help you better understand the hierarchy of the second sample command, we illustrate its structure as follows: + +drawing + +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +#### Adding a deliverer : `add-d` + +Adds a deliverer to your contact list. A deliverer delivers pets from suppliers to buyers. + +Format: `add-d n/NAME ph/PHONE_NUMBER e/EMAIL a/ADDRESS l/LOCATION` + +Examples: + +* To add a single deliverer: `add-d n/Lezheng ph/19657471 e/lez998@u.nus.edu a/PGP Residences 118429 l/Singapore` + +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +#### Adding a supplier : `add-s` + +Adds a supplier to the contact list. A supplier feeds, trains, and takes care of pets for sale. + +You can add a supplier without pets as shown below. + +Format: `add-s n/NAME ph/PHONE_NUMBER e/EMAIL a/ADDRESS l/LOCATION` + +Example: +* To add a single supplier: `add-s n/Carol Pet House ph/11223344 e/carolpethouse@gmail.com a/Marina Bay Sands 138600 l/USA` + +Similar to the [Add Buyer](#adding-a-buyer-add-b) command, you may feel the need to add a supplier together with all the pets he/she sells in one shot. +Check it out below :point_down: + +Format: `add-s n/NAME ph/PHONE_NUMBER e/EMAIL a/ADDRESS l/LOCATION p/add-p(pet1 prefixeds and parameters) p/add-p(pet2 prefixeds and parameters)…​` + +
+ +:bulb: **Tip:** Note that you can input as many `p/add-p` prefixes as you need. Each `p/add-p` creates an order under the buyer. +After each `p/add-p`, simply enter the details for the pet without specifying the index of the associated supplier. +Don't know how to add a pet properly? Refer to [Add Pet](#adding-a-pet-to-a-supplier--add-p) for more information. + +
+ +
+ +:bulb: **Tip:** For more details on what each prefix represents, kindly refer to [List of Prefixes](#list-of-prefixes). + +
+ +Examples: +* To add a supplier with one pet for + sale: `add-s n/Carol Pet House ph/17238965 e/carolpethouse@gmail.com a/Marina Bay Sands 138600 l/Singapore p/add-p p_n/Luck p_d/2022-01-01 p_c/pink p_cp/pure pink p_h/41.2 p_s/Yorkshire pig p_cert/US certified p_v/true p_w/102.5 p_p/270.3` +* To add a supplier with two pets for + sale: `add-s n/Carol Pet House ph/17238965 e/carolpethouse@gmail.com a/Marina Bay Sands 138600 l/Singapore p/add-p p_n/Luck p_d/2022-01-01 p_c/pink p_cp/pure pink p_h/41.2 p_s/Yorkshire pig p_cert/US certified p_v/true p_w/102.5 p_p/270.3 p/add-p p_n/Snupy p_d/2021-05-31 p_c/white p_cp/dotted p_h/89.3 p_cert/US certified p_s/Californian rabbit p_v/false p_w/32.8 p_p/330.3` + +
+ +:exclamation: **Caution**: Take note that the above added suppliers are considered as the SAME, so if you try all these sample commands, PetCode will notify you that the supplier already exists in the list. +Check out [FAQ](#faq) on the concept of "What is being the same". + +
+ +
+ +:bulb: **Tip:** **Overwhelmed by the prefixes** when adding multiple pets? +Check out [Add Command using the popup window](#adding-a-contact-with-a-popup-window--add) to add multiple pets when adding a supplier **without prefixes**. + +
+ +To help you better understand the hierarchy of the second sample command, we illustrate its structure as follows: + +drawing + +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +#### Adding an order to a buyer : `add-o` + +Adds an order to a buyer contact. This is especially useful when an existing buyer has a new order, or when the buyer confirms the order some time after being added to the contacts. + +Format: `add-o INDEX_OF_BUYER o_st/STATUS o_r/add-r o_a/AGE o_sp/SPECIES o_c/COLOR o_cp/COLOR_PATTERN o_p/PRICE o_pr/PRICE_RANGE o_d/DATE [o_ar/ADDITIONAL_REQUEST]…​` + +
+ +:exclamation: **Caution**: `INDEX_OF_BUYER` should be immediately after `add-o`. +Ensure that at the index is a buyer before executing this command, +which can be achieved by executing the [List Command](#listing-contacts-or-items--list) or [Find buyer command](#finding-a-buyer--find-b) beforehand.
+
+ +
+ +:exclamation: **Caution**: Please ensure that `o_r/` is followed by `add-r` immediately and there are no other prefixes +between `o_r/`, `o_a/`, `o_c/`, `o_cp/`, and `o_sp/`. This is because they as a whole specify how the requested pet +should be like. + +
+ +
+ +:information_source: **What is a request in an order?**
+ +The prefix `o_r/` specifies a request, which contains the **characteristics of a pet** that the buyer wants the pet to have, +including age, color, color pattern and species. +Other information in the order that is **not directly related to a pet**, such as the order status and the price range the buyer is willing to accept, **does not fall under request**. + +
+ +
+ +:bulb: **Tip:** For more details on what each prefix represents, kindly refer to [List of Prefixes](#list-of-prefixes). + +
+ +Examples: + +* To add an order to the buyer at index 1 of the display list: `add-o 1 o_st/Pending o_r/add-r o_a/1 o_sp/German shepherd o_c/golden o_cp/pure color o_p/30 o_pr/20, 50 o_d/2022-10-26 o_ar/vaccinated o_ar/free delivery` +* To add an order to the buyer at index 2 of the display list: `add-o 2 o_st/Negotiating o_r/add-r o_a/3 o_sp/chihuahua o_c/white o_cp/dotted white o_p/44.1 o_pr/10.6, -1 o_d/2022-09-20 o_ar/noble blood o_ar/not naughty` + +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +#### Adding a pet to a supplier : `add-p` + +Adds a pet to a supplier contact. This is especially useful when an existing supplier has a new pet for sale or when the supplier tells you what are the pets he/she owns some time after being added to the contacts. + +Format: `add-p INDEX_OF_SUPPLIER p_n/PET_NAME p_d/DATE_OF_BIRTH p_c/COLOR p_cp/COLOR_PATTERN p_h/HEIGHT p_w/WEIGHT p_s/SPECIES p_v/VACCINATION_STATUS p_p/PRICE [p_cert/CERTIFICATE]…​` + +
+ +:exclamation: **Caution**: `INDEX_OF_SUPPLIER` should be immediately after `add-p`. +Ensure that at the index is a supplier before executing this command, +which can be achieving by executing the [List Command](#listing-contacts-or-items--list) or [Find supplier command](#finding-a-supplier--find-s) beforehand. + +
+ +Examples: + +* To add a pet to the supplier at index 1 of the display list: `add-p 1 p_n/Kawaii p_d/2001-11-20 p_c/red p_cp/stripped p_h/39.5 p_s/Bengal cat p_v/true p_w/15.3 p_p/20 p_cert/GoodDog Cert p_cert/Royal Blood Cert` + +
+ +:bulb: **Tip:** For more details on what each prefix represents, kindly refer to [List of Prefixes](#list-of-prefixes). + +
+ +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +#### Adding a contact with a popup window : `add` -### Adding a person: `add` +Adds a contact with a popup window that has prompt texts for what to input without the need to enter any +prefixes. This reduces the need to memorise prefixes. +Given below is the popup window for adding a supplier. -Adds a person to the address book. +![pop up window for adding a supplier](images/AddSupplierWithPopup.png) -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +The followings are two ways to use this command: + +* Adds a buyer to the contacts with or without any number of orders. + + Format: `add buyer` + +* Adds a supplier to the contacts with or without any number of pets. + + Format: `add supplier` + +
+ +**:bulb: Tip:** **Useful keyboard shortcuts for the pop-up window:** + +| Keyboard shortcut | Associated action | +|:-----------------:|:---------------------------------------------------------------------------------------| +| ESCAPE | Closes the popup window **without saving** | +| ENTER | Goes to the next input text field | +| CTRL + A | Adds an order/pet to the buyer/supplier | +| CTRL + D | Deletes the last order/pet under the buyer/supplier in the popup window | +| CTRL + S | Saves the inputs, adds the buyer/supplier to the contacts, and closes the popup window | +| CTRL + D | Deletes the last order/pet under the buyer/supplier in the pop-up window | +| CTRL + S | Saves the inputs, adds the buyer/supplier to the contacts, and closes the pop-p window |

+ +Note that some shortcuts are only **effective when a text field is in focus**. +When no text fields are highlighted (i.e. not in focus), **press TAB once (still no focus, press TAB again and again until the highlight appears)** to focus the cursor to a text field. +This ensures that you can use all the available shortcuts. + +
+ +
+ +:information_source: **Note**: If a compulsory text field is ***empty*** or a text field ***starts with whitespace*** during saving, the cursor will be brought to that text field, +which will be highlighted in red. + +
+ +
+ +:information_source: **Note**: If the input of a text field is in the ***wrong format*** during saving, the person will not be added to +the contacts and the pop-up window will not close. +The error message and the correct format of the input will be shown in the **main window**. + +
+ +
+ +:exclamation: **Caution**: This command is only available for **adding a buyer or supplier** for the current version. +Note that the three buttons for adding pets to a supplier, which are `Upload photo`, `Upload pet certificate`, `Upload vaccination proof` (as seen in the image above), +only **open the file explorer** and **do nothing more**. You may be able to upload files from your local disk to the storage of PetCode in future versions. + +
+ +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +### Matching pets to an order : `match` + +Matches the "best fit" pet to an order. This is especially useful when you receive an order +and want to find out **which pet(s)** on sale is the **best fit** (i.e. description of the pet matches as many requirements specified in the order as possible). +With this information, you may contact the suppliers who own these pets for further negotiation. + +
+ +:information_source: **How does the match command work?**
+ +We have designed an algorithm to give each pet in the storage a score. Pets with descriptions that are closer to the requirements specified in the order will be given a higher score. +Pets with higher scores (i.e. more fitting to the order) are displayed on top. If you want to know how we design the algorithm, check out our [Developer Guide](DeveloperGuide.md).
+ +In the current version of PetCode, the score calculation in the algorithm uses a default set of weightages. In the future, you may be able to define your own +weightages for different parameters, such as price, age, species and so on. + +
+ +Format: `match INDEX` + +Example: +* To match the first order in the display list to pets in the storage: `match 1` + +
+ +:exclamation: **Caution**: Please ensure that at the index is an order, which can be achieved by executing the [List command](#listing-contacts-or-items--list) or [Filter order command](#filtering-orders--filter-o) beforehand. + +
+ +Using the original set of sample data **without any modification**, the following picture shows the display list before the +match command is executed. The original order is `Shiro`, `Ashy`, `Plum`, `Page`, `Snowy`, and `Buddy`. + +![before match command](images/BeforeMatch.png) + +The following picture shows the display list after `match 1` is executed. Now the order is `Snowy`, `Page`, `Plum` +, `Ashy`, `Shiro`, and `Buddy`. + +![after match command](images/AfterMatch.png) + +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +### Deleting a contact or item : `delete` + +Deletes a contact / item at the specified index of the respective contact / item list. + +Format: `delete-KEY INDEX` + +#### KEY Table + +| Contact / Item to Delete | KEY | +|:------------------------:|:---:| +| Buyer | b | +| Supplier | s | +| Deliverer | d | +| Order | o | +| Pet | p | + +Examples: +* `delete-b 1`, deletes `Buyer` contact at index 1 of the display list, if index is found. +* `delete-s 2`, deletes `Supplier` contact at index 2 of the display list, if index is found. +* `delete-d 1`, deletes `Deliverer` contact at index 1 of the display list, if index is found. +* `delete-o 1`, deletes `Order` at index 1 of the display list, if index is found. +* `delete-p 1`, deletes `Pet` at index 1 of the display list, if index is found. + +
+ +:exclamation: **Caution**: Please ensure that you have the corresponding display list before you execute the delete command. +For example, if you want to delete a buyer by `delete-b 1`, do ensure at index 1 is a buyer. +This is to make sure you know what you are deleting and do not delete a contact or item by accident, since there is no `undo` command yet.
+ + * To ensure at the index is a contact, use [List command](#listing-contacts-or-items--list) or [Find command](#finding-contacts-using-keywords--find).
+ * To ensure at the index is an item, use [List command](#listing-contacts-or-items--list) or [Filter command](#filtering-items-by-attributes--filter). + +
+ +
+ +**:bulb: Tip:** Want to delete an order or a pet when browsing through the list of buyer / supplier? +You **don't need to** list all orders / pets, find that particular order / pet and its index, and delete from there. +Instead, you can just use the [Check command](#checking-which-item-belongs-to-which-contact--check), `check buyer 1` for example, and delete from the order list of that buyer. + +
+ +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +### Editing attribute(s) of a contact : `edit` + +Edits one or more attributes of a contact by the index number used in the displayed contacts list. +Existing values of that attribute will be overwritten by the input values. +Please provide **at least one** attribute. + +Format: `edit-KEY INDEX [n/NAME] [ph/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [l/LOCATION]` + +#### KEY Table + +| Contact to Edit | Role | +|:---------------:|:----:| +| Buyer | b | +| Deliverer | d | +| Supplier | s | + +
+ +:exclamation: **Caution**: Please ensure that you have the corresponding display list before you execute the edit command. +For example, if you want to edit a buyer by `edit-b 1`, do ensure at index 1 is a buyer. + +
+ +Examples: +* `edit-b 1 n/Alex`, modifies the name of the Buyer contact at index 1 of Buyer List to Alex, if index is found. +* `edit-s 3 n/Bobby ph/884321` modifies the name to Bobby and phone to 884321, of the Supplier contact at index 3 of Supplier List to Alex, if index is found. + +
+ +:exclamation: This command is only available for **editing the basic information** of a contact for the current version. In other words, **information regarding `Order`/`Pet`** that the `Buyer`/`Supplier` possess **cannot be modified**. + +
+ +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +### Finding contact(s) using keywords : `find` + +Displays **all** contacts which match **one** specific attribute. This command is especially useful when you want to quickly +find contacts based on an attribute. + +Format: `find PREFIX/ATTRIBUTE` + +There are five possible attributes for finding contact(s): +address, email, location, name, phone. Please provide **one** out of the five when using this command. + +#### Attributes and Their Corresponding Prefixes Table + +| Attribute | Prefix | Format | Example | +|-----------|--------|-----------|------------------------| +| Address | a | a/KEYWORD | a/Wall Street | +| Email | e | e/KEYWORD | e/whereisamy@gmail.com | +| Location | l | l/KEYWORD | l/Nova Scotia | +| Name | n | n/KEYWORD | n/Amy Toh | +| Phone | ph | ph/NUMBER | ph/81234567 | + +Examples: + +* `find a/6th College Ave West`, looks for and displays (if any) any person who lives at this address. +* `find e/blackball@furry.com`, looks for and displays (if any) any person who has this email address. +* `find ph/98986668`, looks for and displays (if any) any person whose phone number is that. + +
+ +:information_source: **Notes**:
+ + * Only **one** attribute is allowed. For example, `find a/6th College Ave West ph/98986668` and `find ph/98986668 ph/98986677` are not allowed.
+ * This command is case-insensitive, meaning `find a/Wall Street` is equivalent to `find a/wall street`.
+ * The above principles also apply to the sub-commands of `find` given below. -
:bulb: **Tip:** -A person can have any number of tags (including 0)
+[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +#### Finding a buyer : `find-b` + +Displays all buyers who match **one** specific attribute. + +Format: `find-b PREFIX/ATTRIBUTE` + +Check out the [Attributes and Their Corresponding Prefixes Table](#attributes-and-their-corresponding-prefixes-table) +for more information on prefixes and attributes. + 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` -### Listing all persons : `list` +* `find-b a/6th College Ave West`, looks for and displays any `Buyer` who lives at this address. +* `find-b e/blackball@furry.com`, looks for and displays any `Buyer` who has this email address. +* `find-b ph/98986668`, looks for and displays any `Buyer` whose phone number is that. + +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +#### Finding a deliverer : `find-d` + +Displays all deliverers who match **one** specific attribute. + +Format: `find-d PREFIX/ATTRIBUTE` + +Check out the [Attributes and Their Corresponding Prefixes Table](#attributes-and-their-corresponding-prefixes-table) +for more information on prefixes and attributes. + +Examples: -Shows a list of all persons in the address book. +* `find-d a/6th College Ave West`, looks for and displays any `Deliverer` who lives at this address. +* `find-d e/blackball@furry.com`, looks for and displays any `Deliverer` who has this email address. +* `find-d ph/98986668`, looks for and displays any `Deliverer` whose phone number is that. -Format: `list` +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] -### Editing a person : `edit` +#### Finding a supplier : `find-s` -Edits an existing person in the address book. +Displays all suppliers who match **one** specific attribute. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Format: `find-s PREFIX/ATTRIBUTE` -* 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. +Check out the [Attributes and Their Corresponding Prefixes Table](#attributes-and-their-corresponding-prefixes-table) +for more information on prefixes and attributes. 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. -### Locating persons by name: `find` +* `find-s a/6th College Ave West`, looks for and displays any `Supplier` who lives at this address. +* `find-s e/blackball@furry.com`, looks for and displays any `Supplier` who has this email address. +* `find-s ph/98986668`, looks for and displays any `Supplier` whose phone number is that. + +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +### Filtering items by attributes : `filter` + +Displays items based on the specified attribute(s). This command is especially useful when you want to coordinate +sales between a Buyer and Supplier, by filtering out orders that are still "Pending", or filtering out pets with specific attributes. +Please provide **at least one** attribute when using this command. + +
+ +:information_source: **What is the difference between find command and filter command?**
+ * Find command: Finds **contact(s)**; only **one** attribute is allowed.
+ * Filter command: Filters **item(s)**; **multiple** attributes are allowed. + +
+ +
+ +:information_source: **Notes**:
+ + * This command is case-insensitive, meaning `filter-o o_st/Pending` is equivalent to `filter-o o_st/pending`.
+ * Having multiple prefixes of the same type is allowed, but only the latest input will be taken. + For example, `filter-o o_st/Pending o_st/Delivering` is equivalent to `filter-o o_st/Delivering`.
+ * When multiple attributes are given, items that fulfil **all** attributes are filtered out. + +
+ +#### Filtering orders : `filter-o` + +Displays Orders that satisfy the given attribute(s). There are three possible attributes to filter: additional +requests, order status, and price range. + +Format: `filter-o PREFIX/ATTRIBUTE [PREFIX/ATTRIBUTE]…​` + +| Attribute | Prefix | Format | Example | +|---------------------|--------|-------------------------------|--------------------| +| Additional requests | o_ar | o_ar/KEYWORD | o_ar/free delivery | +| Order Status | o_st | o_st/KEYWORD | o_st/Negotiating | +| Price Range | o_pr | o_pr/LOWER_BOUND, UPPER_BOUND | o_pr/100, 456 | + +
+ +:exclamation: **Caution**: Order status can only be one of the following three: `Pending`, `Negotiating`, `Delivering`. + +
-Finds persons whose names contain any of the given keywords. +
-Format: `find KEYWORD [MORE_KEYWORDS]` +:information_source: Filtering based on price range will filter out orders with price ranges that are **subsets** of the price range specified by the user. +For example, the user input `100, 200` will filter out orders with price ranges `100, 150` and `180, 200`, but not `80, 140` and `175, 230`. -* 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` +
Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) -### Deleting a person : `delete` +* `filter-o o_st/Pending`, filters out orders with order status set to `Pending` +* `filter-o o_st/Negotiating o_pr/90, 900`, filters out orders with order status set to`Negotiating` and price ranges that are subsets of `90, 900` +* `filter-o o_ar/good o_st/Delivering o_pr/80, 100`, filters out orders with additional requests that contain the keyword `good`, order status set to `Delivering` and price ranges that are subsets of `80, 100`. + +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] -Deletes the specified person from the address book. +#### Filtering pets : `filter-p` -Format: `delete INDEX` +Displays Pets that satisfy the given attributes. There are five possible attributes to filter: color, name, +price, species, vaccination status. -* 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, …​ +Format: `filter-p PREFIX/ATTRIBUTE [PREFIX/ATTRIBUTE]…​` + +| Attribute | Prefix | Format | Example | +|--------------------|--------|-------------|----------------| +| Color | p_c | p_c/KEYWORD | p_c/pink | +| Name | p_n | p_n/KEYWORD | p_n/snow white | +| Price | p_p | p_p/NUMBER | p_p/209 | +| Species | p_s | p_s/KEYWORD | p_s/ostrich | +| Vaccination Status | p_v | p_v/KEYWORD | p_v/false | 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. + +* `filter-p p_c/white`, filters out pets of white color +* `filter-p p_c/black p_v/true`, filters out vaccinated pets of black color +* `filter-p p_c/grey p_n/doraemon p_p/50 p_s/cat p_v/true`, filters out vaccinated cats of grey color named doraemon sold at a price of $50 + +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +### Sorting contacts and items : `sort` + +Sorts the specified list based on the default or given attribute(s) as sorting criteria. + +Format: `sort KEY [ATTRIBUTE]…​` + +#### KEY and ATTRIBUTE Table + +| Contact / Item to Sort | KEY | Default Sorting Criteria | Possible ATTRIBUTE to Sort
(acceptable parameters) | Examples | +|:----------------------:|:------------:|:------------------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------| +| Buyer | buyer, b | Number of orders | Name (name, n),
Phone (phone, ph),
Email (email, e),
Location (location, l),
Address (address, a),
Number of Orders(order)
| `sort buyer name`,
`sort b p n` | +| Supplier | supplier, s | Number of pets | Name (name, n),
Phone (phone, ph),
Email (email, e),
Location (location, l),
Address (address, a),
Number of Pets(pet)
| `sort supplier e`,
`sort s address` | +| Deliverer | deliverer, d | Name | Name (name, n),
Phone (phone, ph),
Email (email, e),
Location (location, l),
Address (address, a),
Number of Orders(order)
| `sort d location`,
`sort deliverer n` | +| Order | order, o | Due date | Due Date (duedate, d),
Price Range (pricerange, pr),
Settled Price (price, p),
Order Status (orderstatus, os)
| `sort order pr`, `sort o d p os` | +| Pet | pet, p | Price | Price (price, p),
Name (name, n),
Color (color, c),
Color Pattern (colorpattern, cp),
Birth Date (birthdate, bd),
Species (species, s),
Height (height, h),
Weight (weight, w) | `sort pet color`, `sort p s cp` | + +
+ +:information_source: When no attributes are specified as sorting criteria, the list will be sorted based on the default sorting criteria given above. +When multiple attributes are provided, the list will be first sorted based on the first attribute, and then based on the second, and so on. For example, `sort pet price height` sorts the pet list by price first. Pets with the same price are sorted by their height. + +
+ +
+ +:exclamation: This command sorts the specified list in **ascending** order. For example, it will display the buyer with the **lowest** number +of orders at the **top** of the buyer list and the buyer with the **highest** number of orders at the **bottom** of the buyer list. + +
+ +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] ### Clearing all entries : `clear` -Clears all entries from the address book. +Clears all entries from the PetCode. + +
+ +:exclamation: **Caution:** Be careful when using this command as there is no `undo` command implemented yet. + +
Format: `clear` +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + ### Exiting the program : `exit` Exits the program. Format: `exit` +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +### Automation of information flow `[coming in v2.0]` + +In future versions of PetCode, some information will be auto-updated. For example, you will be able to upload vaccination proof and pet certificates to PetCode from your local disk. +`Vaccination status` will be set to `true` once the system detects the vaccination proof file, and `pet certificates` will be set to the titles of the pet certificate documents uploaded. +You will also be able to transfer orders from buyers to suppliers and then to deliverers. The `order status` will be auto-updated from `Pending` to `Negotiating` to `Delivering` in the process. + +This is the reason why you are unable to modify some information or interact with certain UI elements, and why some information, although you can enter and store in PetCode, is not displayed in the UI, in the current version. + +[Go back to [Table of Contents](#table-of-contents)] +[Go back to [Commands](#commands)] + +## How data is stored ### 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. +PetCode data is saved into your computer's hard disk automatically after any command that changes the data. +There is no need to save manually. ### Editing the data file -AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +PetCode data is saved as a JSON file `[JAR file location]/data/addressbook.json`. +Advanced users are welcome to update the data directly by editing that data file. + +
+ +:exclamation: **Caution:** Please do not edit the id and ids that are stored in the data file. These ids acted as primary keys and foreign keys and are used to recognise the relationship between order/pet and buyer/supplier. + +
+ +
+ +:exclamation: **Caution:** If your changes to the data file makes its format invalid, PetCode will discard all data and +start with an empty data file at the next run. For certain data changes, PetCode will discard the change and initialise the respective data value to its default value. -
: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.
### Archiving data files `[coming in v2.0]` _Details coming soon ..._ +[Go back to [Table of Contents](#table-of-contents)] + -------------------------------------------------------------------------------------------------------------------- ## FAQ **Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**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 PetCode home folder. + +**Q**: What is being the same? Why does the app tell me that the buyer/deliverer/supplier already exits in the list? How +are contacts and items considered as duplicates?
+**A**: Unfortunately, we do not allow duplicate contacts or items in our app, otherwise you may mistakenly modify a +person that you don't intend to! For buyer/deliverer/supplier, if they are of the same person category and have the same +name and the same email address, they are considered as the same. However, we do allow a buyer and a deliverer to have +exactly the same name and the same email, even all attributes being identical. That is, we allow a person to have more +than one role in our app. +Two pets are considered as the same if they have all the same attributes. Two orders are different even they have all +the same attributes. +For example, `"John"` is the same as `"John"`, not the same as `"john"`, `" john "`, or `"jOhn"`. + +[Go back to [Table of Contents](#table-of-contents)] -------------------------------------------------------------------------------------------------------------------- -## Command summary - -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`
e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` -**List** | `list` -**Help** | `help` +## Summaries +### List of Prefixes + +These prefixes are for you to indicate different parameters when you add a new [buyer](#adding-a-buyer-add-b), a new [deliverer](#adding-a-deliverer-add-d), a new [supplier](#adding-a-supplier-add-s), a new [order](#adding-an-order-to-a-buyer-add-o), or a new [pet](#adding-a-pet-to-a-supplier--add-p). + +| Prefix | Category | Meaning | Usage | Example | +|-----------|-----------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------| +| `n/` | General Person | Name | A string of alphanumeric and characters and whitespaces. Required. | `n/John Halstead` | +| `ph/` | General Person | Phone number | Numbers only. Required. | `ph/80334455` | +| `e/` | General Person | Email address | A string of characters. Must contain `@` and follow email format. Required. | `e/1324@sample.com` | +| `a/` | General Person | Address | A string of any characters. Required. | `a/36 College Ave East, Singapore 138600` | +| `l/` | General Person | Location (country) of this person | A string of alphanumeric characters. Required. | `l/Singapore`, `USA`, `China` | +| `o/` | Order | Order | Always followed by `add-o`. Optional, if no orders to add when adding a buyer. Can have multiple. | `o/add-o` | +| `o_st/` | Order | Order status | `Pending`, `Negotiating`, or `Delivering` | `o_st/Pending` | +| `o_p/` | Order | Settled price | A non-negative decimal number. Use `-1` to indicate not settled price. Must be within the price range. Required. | `o_p/38.6` | +| `o_pr/` | Order | Acceptable price range | This is for you to use when negotiating with buyer -- the range the price is expected to fall within. Two non-negative decimal numbers, separated by a comma `,`. The first must not be greater than the second unless the second one is `-1`, which is used to indicate that you haven't settled down one or two of the bounds. Required. | `o_pr/-1, -1`, `o_pr/2.9, -1`, `o_pr/4.3, 19.5` | +| `o_d/` | Order | Transaction (scheduled) date | In the format `yyyy-MM-dd`. | `o_d/2022-10-28`, `o_d/2022-9-2` | +| `o_r/` | Order (Request) | Order request | Always followed by `add-r`. The Request group of prefixes describe what kind of pet this order seeks. Required. | `o_r/add-r` | +| `o_a/` | Order (Request) | Requested pet age | A non-negative whole number. Required. | `o_a/5` | +| `o_sp/` | Order (Request) | Requested pet species | A string of alphanumeric and characters and whitespaces. Required. | `o_sp/Chihuahua`, `o_sp/German shepherd` | +| `o_c/` | Order (Request) | Requested pet color | A string of alphanumeric and characters and whitespaces. Required. | `o_c/red` | +| `o_cp/` | Order (Request) | Requested pet color pattern | A string of alphanumeric and characters and whitespaces. This describes the appearance of the pet in more detail. Required. | `o_cp/white stripped`, `o_cp/black dotted` | +| `o_ar/` | Order | Additional request of the order | A string of alphanumeric and characters and whitespaces. Optional. Can have multiple. | `o_ar/free delivery`, `o_ar/arrive in 10 days` | +| `p/` | Pet | Pet | Always followed by `add-p`. Optional, if no orders to add when adding a supplier. Can have multiple. | `p/add-p` | +| `p_n/` | Pet | Pet name | A string of alphanumeric and characters and whitespaces. Required. | `p_n/Page` | +| `p_s/` | Pet | Species | A string of alphanumeric and characters and whitespaces. Required. | `p_s/Chihuahua`, `p_s/German shepherd` | +| `p_d/` | Pet | Date of birth of the pet | In the format `yyyy-MM-dd`. Cannot be a date in future. Required. | `p_d/2020-3-29` | +| `p_c/` | Pet | Color | A string of alphanumeric and characters and whitespaces. Required. | `p_c/blue` | +| `p_cp/` | Pet | Color pattern | A string of alphanumeric and characters and whitespaces. This describes the appearance of the pet in more detail. Required. | `p_cp/blue grids` | +| `p_h/` | Pet | Height | A non-negative decimal number. The unit is cm. Required. | `p_h/33.2` | +| `p_w/` | Pet | Weight | A non-negative decimal number. The unit is kg. Required. | `p_w/58.2` | +| `p_p/` | Pet | Price | A non-negative decimal number. This is the price the pet is to be sold at. Use `-1` to indicate not settled price. Required. | `p_p/55.5` | +| `p_v/` | Pet | Vaccination status | `true` if the pet is vaccinated, otherwise `false`. Required. | `p_v/false` | +| `p_cert/` | Pet | Certificate | A string of alphanumeric and characters and whitespaces. Other certificates this pet holds. Optional. Can have multiple. | `p_cert/US certified`, `p_cert/noble blood` | + +
+ +:information_source: `-0` is also considered as negative. + +
+ +### Command Summary + +| Action | Format | Examples | +|:--------------------------------------------------------------------------:|-------------------------------------------------------------------------------------------|-------------------------------------------------------------------------| +| **[Add](#adding-a-contact-or-item--add)** | `add-ROLE n/NAME b/BREED ph/PHONE_NUMBER e/EMAIL a/ADDRESS l/LOCATION` | `add-b n/Hongyi ph/11223344 e/hhygg@u.nus.edu a/UTR 138600 l/Singapore` | +| **[Add](#adding-a-contact-with-a-popup-window--add)** (using popup window) | `add buyer`, `add supplier` | | +| **[Check](#checking-which-item-belongs-to-which-contact--check)** | `check KEY INDEX` | `check buyer 1` | +| **[Clear](#clearing-all-entries--clear)** | `clear` | | +| **[Delete](#deleting-a-contact-or-item--delete)** | `delete-KEY INDEX` | `delete-b 1`, `delete-d 2`, `delete-s 3`, `delete-o 1`, `delete-p 2` | +| **[Edit](#editing-attributes-of-a-contact--edit)** | `edit-KEY INDEX [n/NAME] [ph/PHONE] [e/EMAIL] [a/ADDRESS] [l/LOCATION]` | `edit-b 1 n/Alex`, `edit-s 3 n/Bobby ph/884321` | +| **[Find](#finding-contacts-using-keywords--find)** | `find PREFIX/ATTRIBUTE` | `find n/James Jake` | +| **[Find Buyer](#finding-a-buyer--find-b)** | `find-b PREFIX/ATTRIBUTE` | `find-b n/James Jake` | +| **[Find Deliverer](#finding-a-deliverer--find-d)** | `find-d PREFIX/ATTRIBUTE` | `find-d n/James Jake` | +| **[Find Supplier](#finding-a-supplier--find-s)** | `find-s PREFIX/ATTRIBUTE` | `find-s n/James Jake` | +| **[Filter Orders](#filtering-orders--filter-o)** | `filter-o PREFIX/ATTRIBUTE` | `filter-o o_ar/non-allergic o_pr/10-100` | +| **[Filter Pets](#filtering-pets--filter-p)** | `filter-p PREFIX/ATTRIBUTE` | `filter-p p_c/white p_s/capybara` | +| **[Help](#viewing-help--help)** | `help` | | +| **[List](#listing-contacts-or-items--list)** | `list all`, `list buyer`, `list supplier`,
`list deliverer`, `list order`, `list pet` | | +| **[Sort](#sorting-contacts--sort)** | `sort KEY [ATTRIBUTE]…​` | `sort pet price height weight` | + +[Go back to [Table of Contents](#table-of-contents)] diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..17fbc5a2674 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "PetCode" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2223S1-CS2103T-T09-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..7cc1562893a 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: "PetCode"; font-size: 32px; } } diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..a579952705d 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -7,13 +7,13 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "delete-b 1" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delete-b 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteBuyer(b) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic diff --git a/docs/diagrams/DeleteCommandParserClasses.puml b/docs/diagrams/DeleteCommandParserClasses.puml new file mode 100644 index 00000000000..45b6f73ad51 --- /dev/null +++ b/docs/diagrams/DeleteCommandParserClasses.puml @@ -0,0 +1,45 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR_T4 +skinparam classBackgroundColor LOGIC_COLOR + +Class "{abstract}\nDeleteCommand" as DeleteCommand +Class DeleteBuyerCommand +Class DeleteDelivererCommand +Class DeleteSupplierCommand +Class DeleteOrderCommand +Class DeletePetCommand + +package "Parser classes"{ +Class "<>\nParser" as Parser +Class AddressBookParser +Class DeleteBuyerCommandParser +Class DeleteSupplierCommandParser +Class DeleteDelivererCommandParser +Class DeleteOrderCommandParser +Class DeletePetCommandParser +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> AddressBookParser + +AddressBookParser .down.> DeleteBuyerCommandParser: creates > +AddressBookParser .down.> DeleteSupplierCommandParser: creates > +AddressBookParser .down.> DeleteDelivererCommandParser: creates > +AddressBookParser .down.> DeleteOrderCommandParser: creates > +AddressBookParser .down.> DeletePetCommandParser: creates > + +DeleteBuyerCommandParser .down.|> Parser +DeleteSupplierCommandParser .down.|> Parser +DeleteDelivererCommandParser .down.|> Parser +DeleteOrderCommandParser .down.|> Parser +DeletePetCommandParser .down.|> Parser + +AddressBookParser ..> DeleteCommand : returns > +DeleteBuyerCommand -up-|> DeleteCommand +DeleteSupplierCommand -up-|> DeleteCommand +DeleteDelivererCommand -up-|> DeleteCommand +DeleteOrderCommand -up-|> DeleteCommand +DeletePetCommand -up-|> DeleteCommand +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..312c6c1e5f5 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -4,8 +4,8 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR -participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR +participant ":DeleteBuyerCommandParser" as DeleteBuyerCommandParser LOGIC_COLOR +participant "d:DeleteBuyerCommand" as DeleteBuyerCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR end box @@ -13,56 +13,56 @@ box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("delete-b 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") +LogicManager -> AddressBookParser : parseCommand("delete-b 1") activate AddressBookParser -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser +create DeleteBuyerCommandParser +AddressBookParser -> DeleteBuyerCommandParser +activate DeleteBuyerCommandParser -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser +DeleteBuyerCommandParser -> DeleteBuyerCommandParser : parse("1") +activate DeleteBuyerCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser +create DeleteBuyerCommand +DeleteBuyerCommandParser -> DeleteBuyerCommand +activate DeleteBuyerCommand -create DeleteCommand -DeleteCommandParser -> DeleteCommand -activate DeleteCommand +DeleteBuyerCommand --> DeleteBuyerCommandParser : d +deactivate DeleteBuyerCommand -DeleteCommand --> DeleteCommandParser : d -deactivate DeleteCommand +DeleteBuyerCommandParser --> DeleteBuyerCommandParser : d +deactivate DeleteBuyerCommandParser -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser +DeleteBuyerCommandParser --> AddressBookParser : d +deactivate DeleteBuyerCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser +DeleteBuyerCommandParser -[hidden]-> AddressBookParser +destroy DeleteBuyerCommandParser AddressBookParser --> LogicManager : d deactivate AddressBookParser -LogicManager -> DeleteCommand : execute() -activate DeleteCommand +LogicManager -> DeleteBuyerCommand : execute() +activate DeleteBuyerCommand -DeleteCommand -> Model : deletePerson(1) +DeleteBuyerCommand -> Model : deleteBuyer(1) activate Model -Model --> DeleteCommand +Model --> DeleteBuyerCommand deactivate Model create CommandResult -DeleteCommand -> CommandResult +DeleteBuyerCommand -> CommandResult activate CommandResult -CommandResult --> DeleteCommand +CommandResult --> DeleteBuyerCommand : result deactivate CommandResult -DeleteCommand --> LogicManager : result -deactivate DeleteCommand +DeleteBuyerCommand --> LogicManager : result +deactivate DeleteBuyerCommand [<--LogicManager deactivate LogicManager diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index d4193173e18..7612e1b6a7b 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -38,7 +38,7 @@ LogicManager --> Storage Storage --[hidden] Model Command .[hidden]up.> Storage Command .right.> Model -note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc +note right of XYZCommand: XYZCommand = AddBuyerCommand, \nFindCommand, etc Logic ..> CommandResult LogicManager .down.> CommandResult diff --git a/docs/diagrams/MatchCommandSequenceDiagram1.puml b/docs/diagrams/MatchCommandSequenceDiagram1.puml new file mode 100644 index 00000000000..54ac7f7a155 --- /dev/null +++ b/docs/diagrams/MatchCommandSequenceDiagram1.puml @@ -0,0 +1,49 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":MatchCommandParser" as MatchCommandParser LOGIC_COLOR +participant "m:MatchCommand" as MatchCommand LOGIC_COLOR +end box + +[-> LogicManager : execute("match 1") +activate LogicManager + +LogicManager -> AddressBookParser +activate AddressBookParser + +create MatchCommandParser +AddressBookParser -> MatchCommandParser +activate MatchCommandParser + +MatchCommandParser -> MatchCommandParser : parse("1") +activate MatchCommandParser + +create MatchCommand +MatchCommandParser -> MatchCommand +activate MatchCommand + +MatchCommand --> MatchCommandParser : m +deactivate MatchCommand + +MatchCommandParser --> MatchCommandParser : m +deactivate MatchCommandParser + +MatchCommandParser --> AddressBookParser : m +deactivate MatchCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +MatchCommandParser -[hidden]-> AddressBookParser +destroy MatchCommandParser + +AddressBookParser --> LogicManager : m +deactivate AddressBookParser + +ref over LogicManager, MatchCommand + execute(model) - execution of the Match Command +end ref + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/MatchCommandSequenceDiagram2.puml b/docs/diagrams/MatchCommandSequenceDiagram2.puml new file mode 100644 index 00000000000..b924c4613fc --- /dev/null +++ b/docs/diagrams/MatchCommandSequenceDiagram2.puml @@ -0,0 +1,87 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant "m:MatchCommand" as MatchCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +participant "petScoreMap:HashMap" as HashMap LOGIC_COLOR +participant "comparator:Comparator" as Comparator LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "model:Model" as Model MODEL_COLOR +participant "grader:PetGrader" as PetGrader MODEL_COLOR +end box + +[-> LogicManager : execute("match 1") +activate LogicManager + +ref over LogicManager, MatchCommand + Creation of MatchCommand +end ref + +LogicManager -> MatchCommand : execute(model) +activate MatchCommand + +create PetGrader +MatchCommand -> PetGrader +activate PetGrader + +PetGrader --> MatchCommand : grader +deactivate PetGrader + +create HashMap +MatchCommand -> HashMap +activate HashMap + +HashMap --> MatchCommand : petScoreMap +deactivate HashMap + +loop until there are no more pets in filteredPets + MatchCommand -> HashMap + activate HashMap + HashMap -> HashMap : put(Pet, PetGrader) + activate HashMap + HashMap -> PetGrader : evaluate(Pet) + activate PetGrader + PetGrader --> HashMap : score + deactivate PetGrader + deactivate HashMap + +end + HashMap --> MatchCommand : petScoreMap + deactivate HashMap + +create Comparator +MatchCommand -> Comparator +activate Comparator + +Comparator --> MatchCommand : comparator +deactivate Comparator + +MatchCommand -> Model : sortPet(comparator) +activate Model + +Model --> MatchCommand +deactivate Model + +MatchCommand -> Model : switchToPetList() +activate Model + +Model --> MatchCommand +deactivate Model + +create CommandResult +MatchCommand -> CommandResult +activate CommandResult + +CommandResult --> MatchCommand +deactivate CommandResult + +MatchCommand --> LogicManager : result +deactivate MatchCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ModelBuyerObjectImplementation.puml b/docs/diagrams/ModelBuyerObjectImplementation.puml new file mode 100644 index 00000000000..bdb93fc2b32 --- /dev/null +++ b/docs/diagrams/ModelBuyerObjectImplementation.puml @@ -0,0 +1,31 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Class AddressBook +Class UniqueBuyerList +Class Buyer +Class Person +Class PersonCategory +Class Name +Class Phone +Class Email +Class Address +Class Tag +Class UniqueId + +AddressBook *--> "1" UniqueBuyerList +Buyer .up.|> Person + +UniqueBuyerList-right->"0..*" Buyer + +Buyer *--> "1" PersonCategory +Buyer *--> "1" Name +Buyer *--> "1" Phone +Buyer *--> "1" Email +Buyer *--> "1" Address +Buyer *--> "*" Tag +Buyer *--> "~* orders" UniqueId +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..841cedc3610 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -12,14 +12,17 @@ Class AddressBook Class ModelManager Class UserPrefs -Class UniquePersonList -Class Person -Class Address -Class Email -Class Name -Class Phone -Class Tag - +Class UniqueBuyerList +Class UniqueDelivererList +Class UniqueSupplierList +Class UniqueOrderList +Class UniquePetList + +Class Buyer +Class Deliverer +Class Supplier +Class Order +Class Pet } Class HiddenOutside #FFFFFF @@ -27,24 +30,34 @@ HiddenOutside ..> Model AddressBook .up.|> ReadOnlyAddressBook -ModelManager .up.|> Model +ModelManager .up.....|> Model Model .right.> ReadOnlyUserPrefs Model .left.> ReadOnlyAddressBook -ModelManager -left-> "1" AddressBook +ModelManager -up-> "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 - -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +UserPrefs .up.....|> ReadOnlyUserPrefs + +AddressBook *--> "1" UniqueBuyerList +AddressBook *--> "1" UniqueDelivererList +AddressBook *--> "1" UniqueSupplierList +AddressBook *--> "1" UniqueOrderList +AddressBook *--> "1" UniquePetList + +UniqueBuyerList-down->"all" Buyer +UniqueDelivererList-down->"all" Deliverer +UniqueSupplierList-down->"all" Supplier +UniqueOrderList-down->"all" Order +UniquePetList-down->"all" Pet + +ModelManager-up-->"filtered" Buyer +ModelManager-up-->"filtered" Deliverer +ModelManager-up-->"filtered" Supplier +ModelManager-up-->"filtered" Order +ModelManager-up-->"filtered" Pet + +UniqueBuyerList -right[hidden]- UniqueSupplierList +UniqueSupplierList -right[hidden]- UniqueDelivererList +UniqueDelivererList -right[hidden]- UniqueOrderList +UniqueOrderList -right[hidden]- UniquePetList -ModelManager -->"~* filtered" Person @enduml diff --git a/docs/diagrams/ModelOrderObjectImplementation b/docs/diagrams/ModelOrderObjectImplementation new file mode 100644 index 00000000000..91a623db6f5 --- /dev/null +++ b/docs/diagrams/ModelOrderObjectImplementation @@ -0,0 +1,34 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Class AddressBook +Class UniqueOrderList +Class Order +Class Buyer +Class PriceRange +Class Request +Class AdditionalRequests +Class LocalDate +Class Price +Class "<>\nOrderStatus" as OrderStatus +Class UniqueId + +AddressBook *--> "1" UniqueOrderList + +UniqueOrderList-right->"0..*" Order + +Order *--> Buyer +Order *--> PriceRange +Order *--> Request +Order *--> AdditionalRequests +Order *--> OrderStatus +Order *--> UniqueId +Order *--> "byDate" LocalDate +Order *--> "settled price" Price + +Price -left[hidden]- LocalDate +LocalDate -left[hidden]- Price +@enduml diff --git a/docs/diagrams/ModelPetObjectImplementation b/docs/diagrams/ModelPetObjectImplementation new file mode 100644 index 00000000000..3d90d22030d --- /dev/null +++ b/docs/diagrams/ModelPetObjectImplementation @@ -0,0 +1,39 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Class AddressBook +Class UniquePetList +Class Supplier +Class Color +Class ColorPattern +Class DateOfBirth +Class Species +Class Weight +Class Height +Class VaccinationStatus +Class UniqueId +Class Price +Class Tag +Class PetCertificate + +AddressBook *--> "1" UniquePetList + +UniquePetList-right->"0..*" Pet + +Pet *--> "1" Supplier +Pet *--> "1" Color +Pet *--> "1" ColorPattern +Pet *--> "1" DateOfBirth +Pet *--> "1" Species +Pet *--> "1" Weight +Pet *--> "1" Height +Pet *--> "1" VaccinationStatus +Pet *--> "1" UniqueId +Pet *--> "1" Price +Pet *--> "0..*" Tag +Pet *--> "0..*" PetCertificate + +@enduml diff --git a/docs/diagrams/ModelSupplierObjectImplementation b/docs/diagrams/ModelSupplierObjectImplementation new file mode 100644 index 00000000000..9ada029a6e6 --- /dev/null +++ b/docs/diagrams/ModelSupplierObjectImplementation @@ -0,0 +1,31 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Class AddressBook +Class UniqueSupplierList +Class Supplier +Class Person +Class PersonCategory +Class Name +Class Phone +Class Email +Class Address +Class Tag +Class UniqueId + +AddressBook *--> "1" UniqueSupplierList +Supplier .up.|> Person + +UniqueSupplierList-right->"0..*" Supplier + +Supplier *--> "1" PersonCategory +Supplier *--> "1" Name +Supplier *--> "1" Phone +Supplier *--> "1" Email +Supplier *--> "1" Address +Supplier *--> "*" Tag +Supplier *--> "~* pets" UniqueId +@enduml diff --git a/docs/diagrams/OldUiClassDiagram.puml b/docs/diagrams/OldUiClassDiagram.puml new file mode 100644 index 00000000000..225c1ebe369 --- /dev/null +++ b/docs/diagrams/OldUiClassDiagram.puml @@ -0,0 +1,35 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class "<>\nUi" as Ui +Class UiManager +Class MainWindow +Class PersonListPanel +Class PersonCard +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +package Logic <> { +Class HiddenLogic #FFFFFF +} + +Class HiddenOutside #FFFFFF +HiddenOutside .right.> Ui + +UiManager --> Logic +MainWindow --> Logic +UiManager ..|> Ui +UiManager --> "1" MainWindow + +MainWindow --> "1" PersonListPanel +PersonListPanel --> "*" PersonCard +PersonCard ..> Model + +@enduml diff --git a/docs/diagrams/PopupWindowActivityDiagram.puml b/docs/diagrams/PopupWindowActivityDiagram.puml new file mode 100644 index 00000000000..aadd5d5079f --- /dev/null +++ b/docs/diagrams/PopupWindowActivityDiagram.puml @@ -0,0 +1,25 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start + +:User types the command in main window; +:Main window creates the pop-up window; +:User inputs information in text fields; +:User saves the inputs; + +while () is ([else]) + if () then ([has empty fields]) + :Pop-up window highlights the empty fields; + else ([else]) + :Main window displays the error message; + endif + :User saves the edited inputs; + +endwhile ([able to generate command from inputs]) +:Pop-up window executes the command; +:Main window displays the result; +:Pop-up window closes; +stop + +@enduml diff --git a/docs/diagrams/PopupWindowClassDiagram.puml b/docs/diagrams/PopupWindowClassDiagram.puml new file mode 100644 index 00000000000..312b9e11428 --- /dev/null +++ b/docs/diagrams/PopupWindowClassDiagram.puml @@ -0,0 +1,64 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class "<>\nUi" as Ui +Class "{abstract}\nUiPart" as UiPart +Class "{abstract}\nPopupPanel" as PopupPanel +Class UiManager +Class MainWindow +Class AddCommandPopupWindow +Class ResultDisplay +Class PopupPanelForBuyer +Class PopupPanelForOrder +Class PopupPanelForPet +Class PopupPanelForSupplier +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +package Logic <> { +Class HiddenLogic #FFFFFF +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Ui +UiManager .left.|> Ui + +UiManager --> Logic +MainWindow -left-> Logic +AddCommandPopupWindow--> Logic + +UiManager --> "1" MainWindow +MainWindow *--> "1" ResultDisplay +MainWindow --> "0..1" AddCommandPopupWindow +AddCommandPopupWindow -left-> "1" ResultDisplay +AddCommandPopupWindow *--> "0..1" PopupPanelForBuyer +AddCommandPopupWindow *--> "0..1" PopupPanelForSupplier + +MainWindow -left-|> UiPart +ResultDisplay --|> UiPart +AddCommandPopupWindow --|> UiPart +PopupPanel --|> UiPart + +PopupPanelForBuyer --|> PopupPanel +PopupPanelForSupplier --|> PopupPanel +PopupPanelForOrder --|> PopupPanel +PopupPanelForPet --|> PopupPanel + +PopupPanelForBuyer --> "*" PopupPanelForOrder +PopupPanelForSupplier --> "*" PopupPanelForPet + +PopupPanelForBuyer ..> Model +PopupPanelForSupplier ..> Model +PopupPanelForOrder ..> Model +PopupPanelForPet ..> Model + +PopupPanel -[hidden]down- Model + +@enduml diff --git a/docs/diagrams/PopupWindowSequenceDiagram1.puml b/docs/diagrams/PopupWindowSequenceDiagram1.puml new file mode 100644 index 00000000000..c6123365c66 --- /dev/null +++ b/docs/diagrams/PopupWindowSequenceDiagram1.puml @@ -0,0 +1,69 @@ +@startuml +!include style.puml + +box Ui UI_COLOR_T1 +participant ":MainWindow" as MainWindow UI_COLOR +participant ":AddCommandPopupWindow" as AddCommandPopupWindow UI_COLOR +participant ":PopupPanelForSupplier" as PopupPanelForSupplier UI_COLOR +end box + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "a:AddCommandWithPopup" as AddCommandWithPopup LOGIC_COLOR +end box + +[-> MainWindow : executeCommand("add supplier") +activate MainWindow + +MainWindow -> LogicManager : execute("add supplier") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand(""add supplier") +activate AddressBookParser + +create AddCommandWithPopup +AddressBookParser -> AddCommandWithPopup +activate AddCommandWithPopup + +AddCommandWithPopup --> AddressBookParser +deactivate AddCommandWithPopup + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> AddCommandWithPopup : execute() +activate AddCommandWithPopup + +AddCommandWithPopup --> LogicManager : command result +deactivate AddCommandWithPopup +AddCommandWithPopup -[hidden]-> LogicManager +destroy AddCommandWithPopup + +LogicManager --> MainWindow : command result +deactivate LogicManager + +MainWindow -> MainWindow : handleAddByPopup("SUPPLIER") +activate MainWindow + +create AddCommandPopupWindow +MainWindow -> AddCommandPopupWindow +activate AddCommandPopupWindow + +create PopupPanelForSupplier +AddCommandPopupWindow -> PopupPanelForSupplier +activate PopupPanelForSupplier + +PopupPanelForSupplier --> AddCommandPopupWindow +deactivate PopupPanelForSupplier + +AddCommandPopupWindow --> MainWindow +deactivate AddCommandPopupWindow + +MainWindow --> MainWindow +deactivate MainWindow + +[<-- MainWindow +deactivate MainWindow + +@enduml diff --git a/docs/diagrams/PopupWindowSequenceDiagram2.puml b/docs/diagrams/PopupWindowSequenceDiagram2.puml new file mode 100644 index 00000000000..41eb2236b79 --- /dev/null +++ b/docs/diagrams/PopupWindowSequenceDiagram2.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml + +box Ui UI_COLOR_T1 +participant ":AddCommandPopupWindow" as AddCommandPopupWindow UI_COLOR +participant ":PopupPanelForSupplier" as PopupPanelForSupplier UI_COLOR +end box + +box Logic LOGIC_COLOR_T1 +participant "<>\nParserUtil" as ParserUtil LOGIC_COLOR +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant "a:AddSupplierCommand" as AddSupplierCommand LOGIC_COLOR +end box + +[-> AddCommandPopupWindow : saving action is detected +activate AddCommandPopupWindow + +AddCommandPopupWindow -> PopupPanelForSupplier : checkAllPartsFilled() +activate PopupPanelForSupplier + +PopupPanelForSupplier --> AddCommandPopupWindow +deactivate PopupPanelForSupplier + +alt all required text fields filled + + AddCommandPopupWindow -> PopupPanelForSupplier : generateCommand() + activate PopupPanelForSupplier + + PopupPanelForSupplier -> PopupPanelForSupplier : generateSupplier() + activate PopupPanelForSupplier + + loop until all inputs are parsed + PopupPanelForSupplier -> ParserUtil : parse attribute + activate ParserUtil + ParserUtil --> PopupPanelForSupplier : parse result + deactivate ParserUtil + end + + PopupPanelForSupplier --> PopupPanelForSupplier : supplier + deactivate PopupPanelForSupplier + + create AddSupplierCommand + PopupPanelForSupplier -> AddSupplierCommand + activate AddSupplierCommand + + AddSupplierCommand --> PopupPanelForSupplier + deactivate AddSupplierCommand + + PopupPanelForSupplier --> AddCommandPopupWindow : a + deactivate PopupPanelForSupplier + + AddCommandPopupWindow -> LogicManager : executeGivenCommand(a) + activate LogicManager + + LogicManager -> AddSupplierCommand : execute() + activate AddSupplierCommand + + AddSupplierCommand --> LogicManager : command result + deactivate AddSupplierCommand + + LogicManager --> AddCommandPopupWindow : command result + deactivate LogicManager + +else at least one compulsory text field not filled + + [<-- AddCommandPopupWindow + deactivate AddCommandPopupWindow + +end + +@enduml diff --git a/docs/diagrams/SortCommandParserClasses.puml b/docs/diagrams/SortCommandParserClasses.puml new file mode 100644 index 00000000000..3a075457117 --- /dev/null +++ b/docs/diagrams/SortCommandParserClasses.puml @@ -0,0 +1,37 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR_T4 +skinparam classBackgroundColor LOGIC_COLOR + +Class "{abstract}\nSortCommand" as SortCommand +Class SortBuyerCommand +Class SortDelivererCommand +Class SortSupplierCommand +Class SortOrderCommand +Class SortPetCommand + +package "Parser classes"{ +Class "<>\nParser" as Parser +Class AddressBookParser +Class SortCommandParser +Class SortCommandParserUtil +Class CommandUtil +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> AddressBookParser + +AddressBookParser .down.> SortCommandParser: creates > + +SortCommandParser ..> SortCommand : creates > +AddressBookParser ..> SortCommand : returns > +SortCommandParser .up.|> Parser +SortCommandParser ..> SortCommandParserUtil +SortCommandParser ..> CommandUtil +SortBuyerCommand -up-|> SortCommand +SortSupplierCommand -up-|> SortCommand +SortDelivererCommand -up-|> SortCommand +SortOrderCommand -up-|> SortCommand +SortPetCommand -up-|> SortCommand +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 760305e0e58..dd718471615 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -18,10 +18,16 @@ package "AddressBook Storage" #F4F6F6{ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook -Class JsonAdaptedPerson -Class JsonAdaptedTag +Class JsonAdaptedBuyer +Class JsonAdaptedSupplier +Class JsonAdaptedDeliverer +Class JsonAdaptedOrder +Class JsonAdaptedPet +Class JsonAdaptedPriceRange +Class JsonAdaptedRequest } + } Class HiddenOutside #FFFFFF @@ -37,7 +43,14 @@ Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedBuyer +JsonSerializableAddressBook --> "*" JsonAdaptedSupplier +JsonSerializableAddressBook --> "*" JsonAdaptedDeliverer +JsonSerializableAddressBook --> "*" JsonAdaptedPet +JsonSerializableAddressBook --> "*" JsonAdaptedOrder +JsonAdaptedPet --> "1" JsonAdaptedSupplier +JsonAdaptedOrder --> " 1" JsonAdaptedBuyer +JsonAdaptedOrder --> "1" JsonAdaptedPriceRange +JsonAdaptedOrder --> "1" JsonAdaptedRequest @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..91fca869338 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -11,14 +11,9 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard Class StatusBarFooter Class CommandBox -} - -package Model <> { -Class HiddenModel #FFFFFF +Class AddCommandPopupWindow } package Logic <> { @@ -32,26 +27,21 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow - -PersonListPanel -down-> "*" PersonCard +MainWindow --> "0..1" AddCommandPopupWindow MainWindow -left-|> UiPart - ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart +AddCommandPopupWindow --|> UiPart -PersonCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic +AddCommandPopupWindow--> Logic -PersonListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/UiClassDiagram1.puml b/docs/diagrams/UiClassDiagram1.puml new file mode 100644 index 00000000000..9169a02629f --- /dev/null +++ b/docs/diagrams/UiClassDiagram1.puml @@ -0,0 +1,79 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class "<>\nUi" as Ui +Class UiManager +Class MainWindow +Class MainListPanel +Class BuyerListPanel +Class DelivererListPanel +Class SupplierListPanel +Class OrderListPanel +Class PetListPanel +Class BuyerCard +Class DelivererCard +Class SupplierCard +Class OrderCard +Class PetCard +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +package Logic <> { +Class HiddenLogic #FFFFFF +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Ui + +UiManager --> Logic +MainWindow -left-> Logic + +UiManager .left.|> Ui +UiManager --> "1" MainWindow +MainWindow *-down-> "1" MainListPanel +MainWindow *-down-> "1" BuyerListPanel +MainWindow *-down-> "1" DelivererListPanel +MainWindow *-down-> "1" SupplierListPanel +MainWindow *-down-> "1" OrderListPanel +MainWindow *-down-> "1" PetListPanel + +/'MainWindow --|> UiPart +BuyerListPanel --|> UiPart +DelivererListPanel --|> UiPart +SupplierListPanel --|> UiPart +OrderListPanel --|> UiPart +PetListPanel --|> UiPart +BuyerCard --|> UiPart +SupplierCard --|> UiPart +DelivererCard --|> UiPart +OrderCard --|> UiPart +PetCard --|> UiPart'/ + +MainListPanel --> "*" BuyerCard +MainListPanel --> "*" DelivererCard +MainListPanel --> "*" SupplierCard +BuyerListPanel --> "*" BuyerCard +DelivererListPanel --> "*" DelivererCard +SupplierListPanel --> "*" SupplierCard +OrderListPanel --> "*" OrderCard +PetListPanel --> "*" PetCard +BuyerCard --> "*" OrderCard +SupplierCard --> "*" PetCard + +BuyerCard .right.> Model +DelivererCard .down.> Model +SupplierCard .down.> Model +OrderCard .down.> Model +PetCard .down.> Model + +OrderCard -[hidden]up- DelivererCard +Model -[hidden]up- OrderCard + +@enduml diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml index 96e30744d24..34885420931 100644 --- a/docs/diagrams/UndoRedoState0.puml +++ b/docs/diagrams/UndoRedoState0.puml @@ -15,6 +15,6 @@ State2 -[hidden]right-> State3 hide State2 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State1 @end diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml index 01fcb9b2b96..71b9f441441 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoRedoState1.puml @@ -3,7 +3,7 @@ skinparam ClassFontColor #000000 skinparam ClassBorderColor #000000 -title After command "delete 5" +title After command "delete-b 5" package States <> { class State1 as "__ab0:AddressBook__" @@ -16,7 +16,7 @@ State2 -[hidden]right-> State3 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml index bccc230a5d1..957f8a14621 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoRedoState2.puml @@ -3,7 +3,7 @@ skinparam ClassFontColor #000000 skinparam ClassBorderColor #000000 -title After command "add n/David" +title After command "add-b n/David ..." package States <> { class State1 as "__ab0:AddressBook__" @@ -14,7 +14,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State3 @end diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml index ea29c9483e4..50bf43b3f34 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoRedoState3.puml @@ -14,7 +14,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml index 1b784cece80..815db7341f5 100644 --- a/docs/diagrams/UndoRedoState4.puml +++ b/docs/diagrams/UndoRedoState4.puml @@ -3,7 +3,7 @@ skinparam ClassFontColor #000000 skinparam ClassBorderColor #000000 -title After command "list" +title After command "list buyer" package States <> { class State1 as "__ab0:AddressBook__" @@ -14,7 +14,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml index 88927be32bc..619766f3177 100644 --- a/docs/diagrams/UndoRedoState5.puml +++ b/docs/diagrams/UndoRedoState5.puml @@ -14,8 +14,8 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State3 note right on link: State ab2 deleted. -@end +@enduml diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml index 410aab4e412..8eff0f69022 100644 --- a/docs/diagrams/UndoSequenceDiagram.puml +++ b/docs/diagrams/UndoSequenceDiagram.puml @@ -48,6 +48,6 @@ deactivate UndoCommand UndoCommand -[hidden]-> LogicManager : result destroy UndoCommand -[<--LogicManager +[<-- LogicManager deactivate LogicManager @enduml diff --git a/docs/images/AddBuyerCommandIllustration.png b/docs/images/AddBuyerCommandIllustration.png new file mode 100644 index 00000000000..62936464654 Binary files /dev/null and b/docs/images/AddBuyerCommandIllustration.png differ diff --git a/docs/images/AddSupplierWithPopup.png b/docs/images/AddSupplierWithPopup.png new file mode 100644 index 00000000000..aaff7cc35b9 Binary files /dev/null and b/docs/images/AddSupplierWithPopup.png differ diff --git a/docs/images/AfterMatch.png b/docs/images/AfterMatch.png new file mode 100644 index 00000000000..578634e22ac Binary files /dev/null and b/docs/images/AfterMatch.png differ diff --git a/docs/images/AlternativeUi.png b/docs/images/AlternativeUi.png new file mode 100644 index 00000000000..7b08a15c435 Binary files /dev/null and b/docs/images/AlternativeUi.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 2f1346869d0..838fa81ed17 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BeforeMatch.png b/docs/images/BeforeMatch.png new file mode 100644 index 00000000000..1c80a354e31 Binary files /dev/null and b/docs/images/BeforeMatch.png differ diff --git a/docs/images/BuyerCard.png b/docs/images/BuyerCard.png new file mode 100644 index 00000000000..2a31198985d Binary files /dev/null and b/docs/images/BuyerCard.png differ diff --git a/docs/images/DeleteCommandParserClasses.png b/docs/images/DeleteCommandParserClasses.png new file mode 100644 index 00000000000..12b38e9143d Binary files /dev/null and b/docs/images/DeleteCommandParserClasses.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..b503a32ca76 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/InterpretGUI.png b/docs/images/InterpretGUI.png new file mode 100644 index 00000000000..ae1a8d30a13 Binary files /dev/null and b/docs/images/InterpretGUI.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index 9e9ba9f79e5..37c950839ff 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/MatchCommandIllustration1.png b/docs/images/MatchCommandIllustration1.png new file mode 100644 index 00000000000..41744f26715 Binary files /dev/null and b/docs/images/MatchCommandIllustration1.png differ diff --git a/docs/images/MatchCommandIllustration2.png b/docs/images/MatchCommandIllustration2.png new file mode 100644 index 00000000000..a0d4ce3026a Binary files /dev/null and b/docs/images/MatchCommandIllustration2.png differ diff --git a/docs/images/MatchCommandSequenceDiagram1.png b/docs/images/MatchCommandSequenceDiagram1.png new file mode 100644 index 00000000000..25899c1870c Binary files /dev/null and b/docs/images/MatchCommandSequenceDiagram1.png differ diff --git a/docs/images/MatchCommandSequenceDiagram2.png b/docs/images/MatchCommandSequenceDiagram2.png new file mode 100644 index 00000000000..bc1bec45ca4 Binary files /dev/null and b/docs/images/MatchCommandSequenceDiagram2.png differ diff --git a/docs/images/ModelBuyerObjectImplementation.png b/docs/images/ModelBuyerObjectImplementation.png new file mode 100644 index 00000000000..6842f032619 Binary files /dev/null and b/docs/images/ModelBuyerObjectImplementation.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..a19b6ac3e56 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ModelOrderObjectImplementation.png b/docs/images/ModelOrderObjectImplementation.png new file mode 100644 index 00000000000..2a2fc4a6bae Binary files /dev/null and b/docs/images/ModelOrderObjectImplementation.png differ diff --git a/docs/images/ModelPetObjectImplementation.png b/docs/images/ModelPetObjectImplementation.png new file mode 100644 index 00000000000..888228cb1a2 Binary files /dev/null and b/docs/images/ModelPetObjectImplementation.png differ diff --git a/docs/images/ModelSupplierObjectImplementation.png b/docs/images/ModelSupplierObjectImplementation.png new file mode 100644 index 00000000000..7fd1919401a Binary files /dev/null and b/docs/images/ModelSupplierObjectImplementation.png differ diff --git a/docs/images/OldUiClassDiagram.png b/docs/images/OldUiClassDiagram.png new file mode 100644 index 00000000000..85cd6f5ef99 Binary files /dev/null and b/docs/images/OldUiClassDiagram.png differ diff --git a/docs/images/PetCode Logo.png b/docs/images/PetCode Logo.png new file mode 100644 index 00000000000..ea7b6e231a3 Binary files /dev/null and b/docs/images/PetCode Logo.png differ diff --git a/docs/images/PopupWindowActivityDiagram.png b/docs/images/PopupWindowActivityDiagram.png new file mode 100644 index 00000000000..6f7788236cc Binary files /dev/null and b/docs/images/PopupWindowActivityDiagram.png differ diff --git a/docs/images/PopupWindowClassDiagram.png b/docs/images/PopupWindowClassDiagram.png new file mode 100644 index 00000000000..f9a9f34467a Binary files /dev/null and b/docs/images/PopupWindowClassDiagram.png differ diff --git a/docs/images/PopupWindowSequenceDiagram1.png b/docs/images/PopupWindowSequenceDiagram1.png new file mode 100644 index 00000000000..ab916013449 Binary files /dev/null and b/docs/images/PopupWindowSequenceDiagram1.png differ diff --git a/docs/images/PopupWindowSequenceDiagram2.png b/docs/images/PopupWindowSequenceDiagram2.png new file mode 100644 index 00000000000..19e990cf4e1 Binary files /dev/null and b/docs/images/PopupWindowSequenceDiagram2.png differ diff --git a/docs/images/SortCommandParserClasses.png b/docs/images/SortCommandParserClasses.png new file mode 100644 index 00000000000..001faef6b5e Binary files /dev/null and b/docs/images/SortCommandParserClasses.png differ diff --git a/docs/images/StartUIPage.png b/docs/images/StartUIPage.png new file mode 100644 index 00000000000..834e20edd7a Binary files /dev/null and b/docs/images/StartUIPage.png differ diff --git a/docs/images/StorageClassDiagram-0.png b/docs/images/StorageClassDiagram-0.png new file mode 100644 index 00000000000..015bd8d15a3 Binary files /dev/null and b/docs/images/StorageClassDiagram-0.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 2533a5c1af0..3a66b64dca0 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..a04a5f35e52 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 785e04dbab4..2d902225135 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UiClassDiagram1.png b/docs/images/UiClassDiagram1.png new file mode 100644 index 00000000000..7399e6c6d6e Binary files /dev/null and b/docs/images/UiClassDiagram1.png differ diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png index 8f7538cd884..74d86faa697 100644 Binary files a/docs/images/UndoRedoState0.png and b/docs/images/UndoRedoState0.png differ diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png index df9908d0948..ded288e83bb 100644 Binary files a/docs/images/UndoRedoState1.png and b/docs/images/UndoRedoState1.png differ diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png index 36519c1015b..d1f2fbb5da5 100644 Binary files a/docs/images/UndoRedoState2.png and b/docs/images/UndoRedoState2.png differ diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png index 19959d01712..daac82c10fe 100644 Binary files a/docs/images/UndoRedoState3.png and b/docs/images/UndoRedoState3.png differ diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png index 4c623e4f2c5..e6a792a25dd 100644 Binary files a/docs/images/UndoRedoState4.png and b/docs/images/UndoRedoState4.png differ diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png index 84ad2afa6bd..fc0c010039d 100644 Binary files a/docs/images/UndoRedoState5.png and b/docs/images/UndoRedoState5.png differ diff --git a/docs/images/boredcoco.png b/docs/images/boredcoco.png new file mode 100644 index 00000000000..b3158ad64ff Binary files /dev/null and b/docs/images/boredcoco.png differ diff --git a/docs/images/elizabethhky.png b/docs/images/elizabethhky.png new file mode 100644 index 00000000000..15e33dc110c Binary files /dev/null and b/docs/images/elizabethhky.png differ diff --git a/docs/images/faithchua.png b/docs/images/faithchua.png new file mode 100644 index 00000000000..b3158ad64ff Binary files /dev/null and b/docs/images/faithchua.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png deleted file mode 100644 index b1f70470137..00000000000 Binary files a/docs/images/helpMessage.png and /dev/null differ diff --git a/docs/images/helpWindow.png b/docs/images/helpWindow.png new file mode 100644 index 00000000000..971cac48230 Binary files /dev/null and b/docs/images/helpWindow.png differ diff --git a/docs/images/hongyi6328.png b/docs/images/hongyi6328.png new file mode 100644 index 00000000000..e487bef52a3 Binary files /dev/null and b/docs/images/hongyi6328.png differ diff --git a/docs/images/matchScoreCalculationFormula.png b/docs/images/matchScoreCalculationFormula.png new file mode 100644 index 00000000000..e353b2ba06e Binary files /dev/null and b/docs/images/matchScoreCalculationFormula.png differ diff --git a/docs/images/uniqueIdIllustration.png b/docs/images/uniqueIdIllustration.png new file mode 100644 index 00000000000..9eae9b63060 Binary files /dev/null and b/docs/images/uniqueIdIllustration.png differ diff --git a/docs/images/wu-lezheng.png b/docs/images/wu-lezheng.png new file mode 100644 index 00000000000..13db2cad426 Binary files /dev/null and b/docs/images/wu-lezheng.png differ diff --git a/docs/images/wweqg.png b/docs/images/wweqg.png new file mode 100644 index 00000000000..be5bf41a5b3 Binary files /dev/null and b/docs/images/wweqg.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..c1fb3c87436 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,16 @@ --- layout: page -title: AddressBook Level-3 +title: PetCode --- -[![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/AY2223S1-CS2103T-T09-2/tp/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-T09-2/tp/branch/master/graph/badge.svg?token=F6VVPXKC9C)](https://codecov.io/gh/AY2223S1-CS2103T-T09-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). +**PetCode is a software app that aims to facilitate better working experience and boost business management efficiency for pet sale coordinators.** 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 PetCode, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing PetCode, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/boredcoco.md b/docs/team/boredcoco.md new file mode 100644 index 00000000000..7eeb22c6120 --- /dev/null +++ b/docs/team/boredcoco.md @@ -0,0 +1,57 @@ +--- +layout: page +title: Faith Chua's Project Portfolio Page +--- + +### Project: PetCode + +PetCode - is a desktop address book application used for coordinating pet deliveries between Pet Suppliers and Pet Customers. + +Given below are my contributions to the project. + +* **New Feature**: *Filter for relevant Pets*. + * What it does: *Using specific attributes, the user can find Pets matching those attributes*. + * Justification: *The user may want to take a quick look at Pets that have specific attributes so that he or she can bulk buy from a Supplier* + * Highlights: *This feature supports entering multiple fields*. + +* **New Feature**: *Filter for relevant Orders*. + * What it does: *Using specific attributes, the user can find Orders matching those attributes*. + * Justification: *The user may want to take a quick look at Orders that have specific attributes so that he or she can have an overview of similar orders.* + * Highlights: *This feature supports entering multiple fields*. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=boredcoco&breakdown=true) + +* **Functionality**: Predicates + * Wrote Predicates (ie. PetNameContainsKeywordsPredicate) for Find and Filter functions + * Predicates pertain to Buyers, Deliverers, Suppliers, Pets and Orders + * These predicates help in the implementation of Find and Filter commands. + +* **Functionality**: Implemented some parser utility methods for predicates. + * Wrote PredicateParser class which generates Predicates pertaining to Buyer, Suppliers, Deliverers, Pets or Orders from the user input. + +* **Project management**: + * *Managed testing and writing test code [\#280](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/280#issue-1428340017)*. + +* **Enhancements to existing features**: + * Find a Buyer, Supplier or Deliver instead of simply finding a person. + +* **Documentation**: + * User Guide: + * Contributed to the "Finding a Contact using Keywords" section of the User guide. + * This includes the following sub-segments: + * Finding a Buyer + * Finding a Supplier + * Finding a Deliverer + * Contributed to the "Filter lists by attributes" section of the User guide. + * This includes the following sub-segments: + * Filtering Orders + * Filtering Pets + * Developer Guide: + * Contributed to explaining how the Logic works. + * Added use cases relevant to the features implemented by me. + * Added the content page of the Developer Guide. + +* **Community**: + * Reported bugs and suggestions for other teams in the class (examples: [/#4](https://github.com/boredcoco/ped/issues/1#issue-1426879221).) + * Most of the bugs were found through writing JUnit tests. + * Some manual testing was done to ensure that UI worked fine. diff --git a/docs/team/elizabethhky.md b/docs/team/elizabethhky.md new file mode 100644 index 00000000000..c18d51105d0 --- /dev/null +++ b/docs/team/elizabethhky.md @@ -0,0 +1,54 @@ +--- +layout: page +title: Hong Ker Yen Elizabeth's Project Portfolio Page +--- + +### Project: PetCode + +PetCode is a software app that aims to facilitate better working experience and boost business management efficiency +for pet sale coordinators. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written +in Java, and has about 30 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Extended the Delete Command to three categories: Delete Buyer Command, Delete Supplier Command and Delete Deliverer Command. + * What it does: allows the user to delete different types of contacts at the specified index. + * Justification: This allows the user to delete contacts that have become outdated. + * Credits: The code was inspired by the original code given in AB3. + +* **New Feature**: Extended the Delete Command for two more categories: Delete Order Command and Delete Pet Command. + * What it does: allows the user to delete an order or pet at the specified index. + * Justification: This allows the user to delete orders that have been completed and pets that are no longer available for sale. + * Credits: The code was inspired by the original code given in AB3. + +* **Functionality**: Added the classes `UniqueOrderIdPredicate` and `UniquePetIdPredicate`. + * What it does: these classes increases testability when testing for unique Orders and unique Pets. + * Justification: Orders and Pets can easily be distinguished by their UniqueId. Hence, when testing for a unique Order or Pet, their UniqueId can be used. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=elizabethhky&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +* **Project management**: + * Kept track and notified teammates of important deadlines. + * Updated demo screenshots for each version release. + +* **Enhancements to existing features**: + * Wrote additional tests for existing features and json files to increase coverage by 6.10% (Pull request [#140](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/140)). + +* **Documentation**: + * User Guide: + * Added an introduction section to provide an overview of our application to new users: [#282](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/282/files), [#315](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/315) + * Added documentation for the feature `delete`: [#191](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/191/files) + * Added a table of contents for easier navigability: [#191](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/191/files) + * Did cosmetic tweaks and proofread the entire user guide to check for typos and consistent tone: [#191](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/191/files), [#285](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/285/files) + * Developer Guide: + * Updated the `Model` Component: [#215](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/215/files) + * Added more UML diagrams for `MatchCommand`, `Logic` component: [#368](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/368) + * Proofread the entire Developer Guide to check for typos and consistent tone: [#368](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/368) + * Added the `Appendix: Effort` section: [#368](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/368) + +* **Community**: + * PRs reviewed (with non-trivial review comments): [#137](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/137) + * Contributed to forum discussions (examples: [#157](https://github.com/nus-cs2103-AY2223S1/forum/issues/157)). + +* **Tools**: + * Used PlantUML to add more UML diagrams in the developer guide. diff --git a/docs/team/hongyi6328.md b/docs/team/hongyi6328.md new file mode 100644 index 00000000000..2d04a3306ca --- /dev/null +++ b/docs/team/hongyi6328.md @@ -0,0 +1,88 @@ +--- +layout: page +title: Huang Hongyi's Project Portfolio Page +--- + +### Project: PetCode +* **New Feature**: Extended the add person command to three categories: add buyer, deliverer command and supplier command. + * What it does: allows the user to add persons according to their roles. + * Justification: This feature is the core feature of this app since all other features are based on it. The user + cannot do further manipulation of data without adding it. + * Highlights: This enhancement defines the paradigm of operations on contacts. That is, every person category has + its + own command word extension for every command, such as `add-s` and `delete-b`. It guides subsequent implementations + of other commands. + +* **New Feature**: Added the add order command and add pet command. + * What it does: allows the user to add an order to a buyer and add a pet to a supplier. Even more convenient, the + user + can choose to add as many orders as possible when adding a buyer, or add as many pets as possible when adding a + supplier. + * Justification: This helps the user organise his/her business by keeping track of orders placed and pets available + for sale. + * Highlights: There are a tremendous number of prefixes and attributes to handle, which was not easy. + +* **New Feature**: Added the match command. + * What it does: allow user to find out which pet for sale is the best fit for an order placed by a buyer. + * Justification: To maximise utility and profit, the user needs to find out the most suitable pet to sell to a buyer + according to the buyer's demand. + * Highlights: The algorithm is a score evaluation algorithm that involves some complex calculations. The weight of + each field is carefully chosen. + +* **Functionality**: Implemented many parser utility methods. + * What it does: provides convenient utility methods to parse strings to other objects and validate data by a variety + of rules. + * Justification: Many commands rely on and make use of these methods for convenience, such as the add commands, the + edit commands, the find commands and the filter commands. + * Highlights: There are many constraints to consider. For example, some data cannot be negative, some can only be + alphanumeric, and some cannot be blank. Multiplicity should be well considered too. + +* **Functionality**: Wrote data models. + * What it does: these data models, such as `Pet`, `Age`, `UniqueOrderList`, and `DateOfBirth` abstract real-world + objects and provide computational paradigms to mimic their interactions. + * Justification: Without these data models, it would be extremely troublesome to manipulate data. + * Highlights: The SOLID principles and OOP were employed thoroughly. +* **Functionality**: Added the unique ID system. +* **Functionality**: Discovered a variety of bugs and fixed them. For + examples, [\#308](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/308) + , [\#304](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/304) + , [\#299](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/299) + , [\#296](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/296) +* **Code + contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=&sort=totalCommits%20dsc&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2022-09-16&tabOpen=true&tabType=zoom&zA=Hongyi6328&zR=AY2223S1-CS2103T-T09-2%2Ftp%5Bmaster%5D&zACS=215.92310030395137&zS=2022-09-16&zFS=&zU=2022-11-01&zMG=false&zFTF=commit&zFGS=groupByRepos&zFR=false) +* **Project management**: + * Managed releases `v1.2` - `v1.3(trial)` (2 releases) on GitHub. + * Assigned teammates to different issues and kept track of their progress. + * Maintained and updated the official and unofficial meeting minutes after every meeting. + * Managed milestones, changed their due dates, and closed them to wrap-up. + * Did final submission of the JAR file, the UG, the DG, and the PPP. + +* **Enhancements to existing features**: + * Let the GUI window fit different screen sizes (Pull + requests [\#296](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/296)) + * Deleted useless classes like `AddCommandParser` and decoupled some classes for higher robustness. + +* **Documentation**: + * User Guide: + * Adding a buyer, Adding a deliverer, Adding a supplier, Adding a pet to a supplier, Adding an order to a buyer, + Prefix Summary, FAQs, Other miscellaneous parts and proofreading + * Developer Guide: + * Target user profile, Value proposition, User stories, Use cases, Match command implementation, Unique ID + implementation, Other miscellaneous parts and proofreading + +* **Community**: + * Created the team's organization and team repo + * Managed issues and allocated tasks to members + * Set up CodeCov repo + * PRs reviewed (with non-trivial review comments): [\#145](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/145) + , [\#174](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/115) + * Contributed to forum discussions (examples: [\#153](https://github.com/nus-cs2103-AY2223S1/forum/issues/153) + , [\#73](https://github.com/nus-cs2103-AY2223S1/forum/issues/73)) + * Reported bugs and suggestions for other teams in the class ( + examples: [\#183](https://github.com/AY2223S1-CS2103T-W17-3/tp/issues/183) + , [\#187](https://github.com/AY2223S1-CS2103T-W17-3/tp/issues/187) + , [\#176](https://github.com/AY2223S1-CS2103T-W17-3/tp/issues/176) + , [\#172](https://github.com/AY2223S1-CS2103T-W17-3/tp/issues/172)) + +* **Tools**: + * Used JavaFX to develop the UI. diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index 773a07794e2..00000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: page -title: John Doe's Project Portfolio Page ---- - -### Project: AddressBook Level 3 - -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -Given below are my contributions to the project. - -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() - -* **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub - -* **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) - -* **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/wu-lezheng.md b/docs/team/wu-lezheng.md new file mode 100644 index 00000000000..3a60f73c1de --- /dev/null +++ b/docs/team/wu-lezheng.md @@ -0,0 +1,71 @@ +--- +layout: page +title: Wu Lezheng's Project Portfolio Page +--- + +### Project: PetCode + +PetCode is a software app that aims to facilitate better working experience and boost business management efficiency for pet sale coordinators. + +Given below are my contributions to the project. + +* **Functionality**: Added the `order` package to the `model` component. (Pull request: [#82](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/82)) + * What it does: Added an `Order` class and its subcomponents. + * Justification: These models lay the foundation so that existing and added operations/commands could be performed using these models. + * Highlights: Some of the SOLID principles of OOP are applied in these classes. + +* **Functionality**: Extended `person` to different categories. (Pull request: [#82](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/82)) + * What it does: Extended the `Person` class into `Buyer`, `Supplier`, and `Deliverer`, with their respective attributes. + * Justification: These models lay the foundation so that existing and added operations/commands could be performed on them. + * Highlights: Some of the SOLID principles of OOP are applied in these classes. + +* **New Feature**: Added a popup window for adding command. (Pull request: [#159](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/159)) + * What it is about: Added a popup window with prompt text for adding buyers with orders and suppliers with pets, without the need to input any prefixes. + * Justification: There is repetitive entering of multiple indexes when the users want to add a buyer with orders or to add a supplier with pets, which is very demanding in terms of memorisation. + * Highlights: The popup window is designed to improve the user experience (UX), with keyboard shortcuts and prompt texts. It also shows how UI, model and logic components can be linked. + +* **New Feature**: Extended the `EditCommand` (Pull request: [#205](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/205)) + * What it does: Extended the `EditCommand` to `EditBuyerCommand`, `EditDelivererommand` and `EditSupplierCommand`. + * Justification: `Buyer`, `Deliverer` and `Supplierr` can have different attributes. Making a separate `EditCommand` for each of them allows customised editing even more distinct attributes are added to them in the future. + * Highlights: This makes use of polymorphism. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=wu-lezheng&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=Wu-Lezheng&tabRepo=AY2223S1-CS2103T-T09-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +* **Project management**: + * Managed releases `v1.3` on GitHub ([Link to v1.3 release](https://github.com/AY2223S1-CS2103T-T09-2/tp/releases/tag/v1.3.1)) + * Recorded and managed the majority of the meeting discussions in the meeting minutes in the Google Drive folder. + * Created and assigned issues to team members on GitHub. + * Created labels and categorised issues on GitHub. + +* **Enhancements to existing features**: + * Updated the style of GUI and its layout. (Pull request: [#142](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/142)) + * Categorised all Java classes into their respective packages. (Pull request: [#205](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/205)) + * Wrote additional tests to increase test coverage. + * Fixed several bugs. (Pull requests: [#325](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/325), [#362](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/362)) + +* **Documentation**: + * User Guide: + * Added documentation for the feature: `Adding a person with a popup window`. (Pull request: [#159](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/159)) + * Added documentation for the feature: `Editing attributes of a contact`. (Pull request: [#205](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/205)) + * Added documentation for the feature: `Listing contacts or items`. (Pull request: [#207](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/207)) + * Proofread the whole UG. + (Pull requests: [#356](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/356), [#364](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/364), [#365](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/365)) + * Developer Guide: + * Updated the `UI component`. (Pull request: [#180](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/180)) + * Added implementation of `Display of person list`. (Pull request: [#180](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/180)) + * Added implementation of `Pop-up window for add command`. (Pull request: [#204](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/204)) + +* **Community**: + * PRs reviewed (with non-trivial comments): + [\#156](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/156#discussion_r1000077198), + [#352](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/352). + * Reported bugs and suggestions for other teams in the class: + [#165](https://github.com/AY2223S1-CS2103T-W08-2/tp/issues/165), + [#170](https://github.com/AY2223S1-CS2103T-W08-2/tp/issues/170), + [#178](https://github.com/AY2223S1-CS2103T-W08-2/tp/issues/178), + [#186](https://github.com/AY2223S1-CS2103T-W08-2/tp/issues/186), + [#195](https://github.com/AY2223S1-CS2103T-W08-2/tp/issues/195). + +* **Tools**: + * Used JavaFX and Scene Builder to modify the UI. + * Used PlantUML to add more UML diagrams in the developer guide. diff --git a/docs/team/wweqg.md b/docs/team/wweqg.md new file mode 100644 index 00000000000..21b60d4e689 --- /dev/null +++ b/docs/team/wweqg.md @@ -0,0 +1,50 @@ +--- +layout: page +title: Zhang Weiqiang's Project Portfolio Page +--- + +### Project: PetCode + +PetCode is a software app that aims to facilitate better working experience and boost business management efficiency for pet sale coordinators. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to sort the items in buyer/supplier/deliverer/order/pet list. + * What it does: It allows user to sort different list by the supported keys in ascending order. + * Justification: This feature improves the product significantly because originally any new order/pet/contact added to the program will be automatically appended to the end of their respective list, there were no ways to reorder the items in the list. If the user wants to prioritize the orders that are more urgent, he/she has to scroll through the entire order list to manually compare and choose. Therefore, the application should provide a convenient way to save the hassle. + * Highlights: This feature supports entering multiple keys for sorting a single list. The later keys are used to break ties arise from sorting using previous keys. For instance, `sort pet height weight` will sort the pets by their heights, for two pets with the same height, their relative sequence will be decided by their weights. + +* **New Feature**: Added the ability to check which item belongs to which contact. + * What it does: It checks a contact at specified index, the application will display different windows for each list input.
For `check buyer 1`, it will display the list of orders from buyer at buyer list index 1.
For `check supplier 1`, it will display the list of pets from supplier at supplier list index 1.
For `check order 1`, it will display the buyer of the order at order list index 1.
For `check pet 1`, it will display the supplier of the pet at pet list index 1.
+ * Justification: This feature improves the product significantly because originally in the order list, the only buyer related information displayed is the name of the buyer. In order to locate the buyer of a particular order, the user has to memorise the name and navigate to the buyer list look for the matched name. This approach is undesirable and is unlikely to identify the correct buyer, as the user could misremember the names or there could be people with the same names. Therefore, the application should provide a convenient way to rectify the problem. + * Highlights: This feature changes the UI display to provide instant feedback to the user. + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=wweqg&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +* **Project management**: + * Update JavaCI and CodeCov badge [\#61](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/61) [\#62](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/62) + * Managed release `v1.3` on GitHub. + +* **Enhancements to existing features**: + * Added five unique lists in the application, added and updated relevant methods in logic, model, storage and other class to fit the changes [\#90](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/90) [\#94](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/94) + * Improved UI interactions for all commands. [\#352](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/352) + * Extended the list command to support five different lists [\#164](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/164) + * Extended the edit command to support three different lists [\#202](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/202) + * Fixed several bugs (examples: [\#222](https://github.com/AY2223S1-CS2103T-T09-2/tp/issues/222), [\#234](https://github.com/AY2223S1-CS2103T-T09-2/tp/issues/234), [\#237](https://github.com/AY2223S1-CS2103T-T09-2/tp/issues/237), [\#239](https://github.com/AY2223S1-CS2103T-T09-2/tp/issues/239), [\#244](https://github.com/AY2223S1-CS2103T-T09-2/tp/issues/244), [\#261](https://github.com/AY2223S1-CS2103T-T09-2/tp/issues/261), [\#262](https://github.com/AY2223S1-CS2103T-T09-2/tp/issues/262), [\#264](https://github.com/AY2223S1-CS2103T-T09-2/tp/issues/264), [\#265](https://github.com/AY2223S1-CS2103T-T09-2/tp/issues/265)) + +* **Documentation**: + * User Guide: + * Added documentation for the features `list`, `sort` and `check` [\#177](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/177/files#diff-b50feaf9240709b6b02fb9584696b012c2a69feeba89e409952cc2f401f373fb) + * Developer Guide: + * Updated the UML diagram for storage package. + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#211](https://github.com/AY2223S1-CS2103T-T09-2/tp/issues/211) [\#305](https://github.com/AY2223S1-CS2103T-T09-2/tp/pull/305) + * Reported bugs and suggestions for other teams in the class (examples: [\#9](https://github.com/wweqg/ped/issues/9), [\#10](https://github.com/wweqg/ped/issues/10), [\#11](https://github.com/wweqg/ped/issues/11), [\#12](https://github.com/wweqg/ped/issues/12).) + +* **Tools**: + * Used JavaFX for UI related features. + * Used PlantUML to add more UML diagrams in the developer guide. + + diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md index 4fb62a83ef6..29553f8f68b 100644 --- a/docs/tutorials/TracingCode.md +++ b/docs/tutorials/TracingCode.md @@ -201,7 +201,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ ``` 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 `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual colorPattern) 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. diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..f889c9df5c9 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -36,7 +36,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 0, true); + public static final Version VERSION = new Version(1, 3, 1, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -79,14 +79,14 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + logger.info("Data file not found. Will be starting with a sample PetCode"); } initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); + logger.warning("Data file not in the correct format. Will be starting with an empty PetCode"); initialData = new AddressBook(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty PetCOde"); initialData = new AddressBook(); } @@ -151,7 +151,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty PetCode"); initializedPrefs = new UserPrefs(); } @@ -167,7 +167,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting PetCode " + MainApp.VERSION); ui.start(primaryStage); } diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java index ba33653be67..c7ccc630496 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/address/commons/core/GuiSettings.java @@ -1,6 +1,7 @@ package seedu.address.commons.core; import java.awt.Point; +import java.awt.Toolkit; import java.io.Serializable; import java.util.Objects; @@ -10,8 +11,8 @@ */ public class GuiSettings implements Serializable { - private static final double DEFAULT_HEIGHT = 600; - private static final double DEFAULT_WIDTH = 740; + private static final double DEFAULT_HEIGHT = Toolkit.getDefaultToolkit().getScreenSize().getHeight() * 0.9; + private static final double DEFAULT_WIDTH = Toolkit.getDefaultToolkit().getScreenSize().getWidth() * 0.9; private final double windowWidth; private final double windowHeight; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..1795ed7575a 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -6,8 +6,18 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_ILLEGAL_VALUE = "Illegal value: "; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The index provided is invalid. " + + "It must be an unsigned whole number. Also, please count from 1 and check whether it is out of range."; + public static final String MESSAGE_MISSING_INDEX = "Missing index!\n"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_ORDERS_LISTED_OVERVIEW = "%1$d orders listed!"; + public static final String MESSAGE_PETS_LISTED_OVERVIEW = "%1$d pets listed!"; + public static final String INVALID_BUYER = "Index %1$s is not a buyer"; + public static final String INVALID_SUPPLIER = "Index %1$s is not a supplier"; + public static final String INVALID_DELIVERER = "Index %1$s is not a deliverer"; + public static final String INVALID_ORDER = "Index %1$s is not an order"; + public static final String INVALID_PET = "Index %1$s is not a pet"; } diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java index 19536439c09..f4b7a593d40 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -9,7 +9,9 @@ * convert it back to an int if the index will not be passed to a different component again. */ public class Index { - private int zeroBasedIndex; + public static final String MESSAGE_USAGE = "Index should be a non-negative whole number."; + + private final int zeroBasedIndex; /** * Index can only be created by calling {@link Index#fromZeroBased(int)} or diff --git a/src/main/java/seedu/address/commons/core/index/UniqueId.java b/src/main/java/seedu/address/commons/core/index/UniqueId.java new file mode 100644 index 00000000000..f84bb4e5b6c --- /dev/null +++ b/src/main/java/seedu/address/commons/core/index/UniqueId.java @@ -0,0 +1,39 @@ +package seedu.address.commons.core.index; + +/** + * Represents a unique id that does not change throughout the life cycle of an Object + */ +public class UniqueId implements Comparable { + private final String id; + + /** + * Constructs a unique id object; + * + * @param id The string as the key for id. + */ + public UniqueId(String id) { + this.id = id; + } + + public String getIdToString() { + return this.id; + } + + @Override + public int compareTo(UniqueId other) { + return this.id.compareTo(other.id); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof UniqueId)) { + return false; + } + return this.id.equals(((UniqueId) other).id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } +} diff --git a/src/main/java/seedu/address/commons/core/index/UniqueIdGenerator.java b/src/main/java/seedu/address/commons/core/index/UniqueIdGenerator.java new file mode 100644 index 00000000000..1983de2c665 --- /dev/null +++ b/src/main/java/seedu/address/commons/core/index/UniqueIdGenerator.java @@ -0,0 +1,87 @@ +package seedu.address.commons.core.index; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Generates a unique ID and increments the current ID. + */ +public class UniqueIdGenerator { + + private static final char START = 'a'; + private static final char BASE = 26; + private static final Set storedIdOrder = new HashSet<>(); + private static final Set storedIdPet = new HashSet<>(); + private final List currentId = new ArrayList<>(); + + /** + * Constructs a unique id generator starting from the base id. + */ + public UniqueIdGenerator() { + currentId.add(START); + } + + /** + * Returns the current id and increments. + * + * @return A unique ID. + */ + public UniqueId next() { + StringBuilder sb = new StringBuilder(); + currentId.forEach(x -> sb.insert(0, x)); + String id = sb.toString(); + increment(); + return new UniqueId(id); + } + + /** + * Adds a unique id to the storedIdPet + */ + public static boolean addToStoredIdPet(UniqueId id) { + return storedIdPet.add(id); + } + + /** + * Checks if the storedIdPet set contains a given id. + */ + public static boolean storedIdPetContains(UniqueId id) { + return storedIdPet.contains(id); + } + + /** + * Adds a unique id to the storedIdOrder. + */ + public static boolean addToStoredIdOrder(UniqueId id) { + return storedIdOrder.add(id); + } + + /** + * Checks if the storedIdOrder set contains a given id. + */ + public static boolean storedIdOrderContains(UniqueId id) { + return storedIdOrder.contains(id); + } + + + private void increment() { + int idx = 0; + boolean carry = false; + do { + char cur = currentId.get(idx); + cur += 1; + if (cur == START + BASE) { + carry = true; + cur = START; + } + currentId.set(idx, cur); + if (carry) { + idx++; + } + } while (carry && idx < currentId.size()); + if (carry && idx == currentId.size()) { + currentId.add(START); + } + } +} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..d74fe8dc1b3 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,25 +4,38 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; 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.order.Order; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; /** - * API of the Logic component + * API of the Logic component. */ public interface Logic { /** * Executes the command and returns the result. * @param commandText The command as entered by the user. - * @return the result of the command execution. + * @return The result of the command execution. * @throws CommandException If an error occurs during command execution. * @throws ParseException If an error occurs during parsing. */ CommandResult execute(String commandText) throws CommandException, ParseException; + /** + * Executes the given command and returns the result. + * @param command The given command. + * @return The result of the command execution. + * @throws CommandException If an error occurs during command execution. + */ + CommandResult executeGivenCommand(Command command) throws CommandException; + /** * Returns the AddressBook. * @@ -30,8 +43,8 @@ public interface Logic { */ ReadOnlyAddressBook getAddressBook(); - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of objects */ + ObservableList getFilteredCurrList(); /** * Returns the user prefs' address book file path. @@ -44,7 +57,33 @@ public interface Logic { GuiSettings getGuiSettings(); /** - * Set the user prefs' GUI settings. + * Sets the user prefs' GUI settings. */ void setGuiSettings(GuiSettings guiSettings); + + /** + * Converts the buyer's orders from a {@code List} to a {@code ObservableList} and returns the result. + * @return An {@code ObservableList} instance containing all the buyer's orders. + */ + ObservableList getOrderAsObservableListFromBuyer(Buyer buyer); + /** + * Converts the delverer's orders from a {@code List} to a {@code ObservableList} and returns the result. + * @return An {@code ObservableList} instance containing all the deliverer's orders. + */ + ObservableList getOrderAsObservableListFromDeliverer(Deliverer deliverer); + /** + * Converts the supplier's pets from a {@code List} to a {@code ObservableList} and returns the result. + * @return An {@code ObservableList} instance containing all the supplier's pets. + */ + ObservableList getPetAsObservableListFromSupplier(Supplier supplier); + + /** + * Switches to buyer list. + */ + void switchToBuyer(); + + /** + * Switches to supplier list. + */ + void switchToSupplier(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..c8f772b3309 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -2,11 +2,15 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.List; import java.util.logging.Logger; +import java.util.stream.Collectors; +import javafx.collections.FXCollections; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.UniqueId; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; @@ -14,7 +18,11 @@ 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.order.Order; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; import seedu.address.storage.Storage; /** @@ -54,14 +62,25 @@ public CommandResult execute(String commandText) throws CommandException, ParseE return commandResult; } + @Override + public CommandResult executeGivenCommand(Command command) throws CommandException { + CommandResult commandResult = command.execute(model); + try { + storage.saveAddressBook(model.getAddressBook()); + } catch (IOException ioe) { + throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); + } + return commandResult; + } + @Override public ReadOnlyAddressBook getAddressBook() { return model.getAddressBook(); } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredCurrList() { + return model.getFilteredCurrList(); } @Override @@ -78,4 +97,49 @@ public GuiSettings getGuiSettings() { public void setGuiSettings(GuiSettings guiSettings) { model.setGuiSettings(guiSettings); } + + @Override + public ObservableList getOrderAsObservableListFromBuyer(Buyer buyer) { + List ids = buyer.getOrderIds(); + List filteredOrders = model.getFilteredOrderList(); + List ordersFromBuyer = filteredOrders.stream() + .filter(order -> ids.stream() + .anyMatch(order::hasId)) + .collect(Collectors.toList()); + + return FXCollections.observableList(ordersFromBuyer); + } + + @Override + public ObservableList getOrderAsObservableListFromDeliverer(Deliverer deliverer) { + List ids = deliverer.getOrders(); + List filteredOrders = model.getFilteredOrderList(); + List ordersFromDeliverer = filteredOrders.stream() + .filter(order -> ids.stream() + .anyMatch(order::hasId)) + .collect(Collectors.toList()); + + return FXCollections.observableList(ordersFromDeliverer); + } + + @Override + public ObservableList getPetAsObservableListFromSupplier(Supplier supplier) { + List ids = supplier.getPetIds(); + List filteredPets = model.getFilteredPetList(); + List petsFromSupplier = filteredPets.stream() + .filter(pet -> ids.stream() + .anyMatch(pet::hasId)) + .collect(Collectors.toList()); + return FXCollections.observableList(petsFromSupplier); + } + + @Override + public void switchToBuyer() { + model.switchToBuyerList(); + } + + @Override + public void switchToSupplier() { + model.switchToSupplierList(); + } } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 71656d7c5c8..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..06235a9650e 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -11,12 +11,13 @@ 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 = "PetCode has been cleared!"; @Override public CommandResult execute(Model model) { requireNonNull(model); + model.clearCurrList(); model.setAddressBook(new AddressBook()); return new CommandResult(MESSAGE_SUCCESS); } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 92f900b7916..96c4ea4ab4a 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -4,6 +4,7 @@ import java.util.Objects; + /** * Represents the result of a command execution. */ @@ -12,18 +13,46 @@ public class CommandResult { private final String feedbackToUser; /** Help information should be shown to the user. */ - private final boolean showHelp; + private final boolean isHelpShown; /** The application should exit. */ - private final boolean exit; + private final boolean isExit; + + /** + * Pop-up window for add command should be shown to the user. + */ + private final boolean isAddedByPopup; + + /** + * Specifies which group to add. + */ + private final String addType; /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, + boolean isHelpShown, + boolean isExit, + boolean isAddedByPopup, + String addType) { this.feedbackToUser = requireNonNull(feedbackToUser); - this.showHelp = showHelp; - this.exit = exit; + this.isHelpShown = isHelpShown; + this.isExit = isExit; + this.isAddedByPopup = isAddedByPopup; + this.addType = addType; + } + + /** + * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, + * and other fields set to their default value. + */ + public CommandResult(String feedbackToUser, boolean isHelpShown, boolean isExit) { + this(feedbackToUser, + isHelpShown, + isExit, + false, + null); } /** @@ -31,19 +60,70 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, + false, + false, + false, + null); + } + + /** + * Constructs a {@code CommandResult} for {@code HelpCommand}. + * @param feedbackToUser Feedback to the user. + * @return A {@code CommandResult} for {@code HelpCommand}. + */ + public static CommandResult createHelpCommandResult(String feedbackToUser) { + return new CommandResult(feedbackToUser, + true, + false, + false, + null); + } + + /** + * Constructs a {@code CommandResult} for {@code ExitCommand}. + * @param feedbackToUser Feedback to the user. + * @return A {@code CommandResult} for {@code ExitCommand}. + */ + public static CommandResult createExitCommandResult(String feedbackToUser) { + return new CommandResult(feedbackToUser, + false, + true, + false, + null); + } + + /** + * Constructs a {@code CommandResult} for {@code AddCommandWithPopup}. + * @param feedbackToUser Feedback to the user. + * @return A {@code CommandResult} for {@code AddCommandWithPopup}. + */ + public static CommandResult createAddByPopupCommandResult(String feedbackToUser, String addType) { + return new CommandResult(feedbackToUser, + false, + false, + true, + addType); } public String getFeedbackToUser() { return feedbackToUser; } - public boolean isShowHelp() { - return showHelp; + public boolean isHelpShown() { + return isHelpShown; } public boolean isExit() { - return exit; + return isExit; + } + + public boolean isAddedByPopup() { + return isAddedByPopup; + } + + public String getAddType() { + return addType; } @Override @@ -59,13 +139,20 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) - && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && isHelpShown == otherCommandResult.isHelpShown + && isExit == otherCommandResult.isExit + && isAddedByPopup == otherCommandResult.isAddedByPopup + && ((addType == null && otherCommandResult.addType == null) + || (addType != null && addType.equals(otherCommandResult.addType))); } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, + isHelpShown, + isExit, + isAddedByPopup, + addType); } } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 02fd256acba..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..30774023488 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -13,7 +13,7 @@ public class ExitCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return CommandResult.createExitCommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT); } } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..09c13a7665d 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -16,6 +16,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return CommandResult.createHelpCommandResult(SHOWING_HELP_MESSAGE); } } 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/MatchCommand.java b/src/main/java/seedu/address/logic/commands/MatchCommand.java new file mode 100644 index 00000000000..adf2a2f1967 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MatchCommand.java @@ -0,0 +1,93 @@ +package seedu.address.logic.commands; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.order.Order; +import seedu.address.model.pet.Pet; +import seedu.address.model.pet.PetGrader; + +/** + * Matches Pets given an Order. + */ +public class MatchCommand extends Command { + + public static final String COMMAND_WORD = "match"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Matches pets given an order, based on how pets fit the request." + + "Parameters: " + + "match " + " INDEX\n" + + "Example: " + + "match " + " 1\n"; + public static final String MESSAGE_SUCCESS = "Matched pets given the order. "; + + private static final Logger LOGGER = LogsCenter.getLogger(MatchCommand.class); + private final Index index; + + /** + * Constructs a MatchCommand object. + * @param index The index of the Order that needs to be matched to Pets. + */ + public MatchCommand(Index index) { + this.index = index; + } + + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException If an error occurs during command execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + ObservableList currList = model.getFilteredCurrList(); + ObservableList petList = model.getFilteredPetList(); + + if (index.getZeroBased() >= currList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Object o = currList.get(index.getZeroBased()); + if (!(o instanceof Order)) { + throw new CommandException(String.format(Messages.INVALID_ORDER, index.getOneBased())); + } + + Order order = (Order) o; + + PetGrader grader = new PetGrader(order); + + Map petScoreMap = new HashMap<>(); + petList.forEach(x -> petScoreMap.put(x, grader.evaluate(x))); + Comparator comparator = (x, y) -> { + if (!petScoreMap.containsKey(y)) { + LOGGER.warning(y.getName() + "'s score is not in the map."); + } + if (!petScoreMap.containsKey(x)) { + LOGGER.warning(x.getName() + "'s score is not in the map."); + } + return Double.compare(petScoreMap.getOrDefault(y, 0.0), + petScoreMap.getOrDefault(x, 0.0)); + }; + + model.sortPet(comparator); + model.switchToPetList(); + + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MatchCommand // instanceof handles nulls + && index.equals(((MatchCommand) other).index)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/addcommands/AddBuyerCommand.java b/src/main/java/seedu/address/logic/commands/addcommands/AddBuyerCommand.java new file mode 100644 index 00000000000..f9a041454af --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/addcommands/AddBuyerCommand.java @@ -0,0 +1,98 @@ +package seedu.address.logic.commands.addcommands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.order.Order; +import seedu.address.model.person.Buyer; + +/** + * Adds a buyer to the address book. + * If this buyer comes with multiple orders (where one order can have a pet as well), these orders and pets + * will be also added to their respective unique lists. + */ +public class AddBuyerCommand extends AddPersonCommand { + + public static final String COMMAND_WORD = "add-b"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a buyer to the address book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_LOCATION + "LOCATION " + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_LOCATION + "USA " + + PREFIX_ORDER + AddOrderCommand.COMMAND_WORD + " (order1 prefixes and parameters) " + + PREFIX_ORDER + AddOrderCommand.COMMAND_WORD + " (order2 prefixes and parameters) "; + + public static final String MESSAGE_SUCCESS = "New buyer added: %1$s"; + public static final String MESSAGE_DUPLICATE_BUYER = "This buyer already exists in the buyer list"; + + private final Buyer toAdd; + private final List orders = new ArrayList<>(); + + /** + * Creates an AddBuyerCommand to add the specified {@code Buyer}. + */ + public AddBuyerCommand(Buyer buyer, List orders) { + requireNonNull(buyer); + toAdd = buyer; + if (orders != null) { + this.orders.addAll(orders); + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasBuyer(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_BUYER); + } + + List orders = this.orders; + int numOrdersAdded = orders.size(); + + for (Order order : orders) { + if (model.hasOrder(order)) { + throw new CommandException(AddOrderCommand.MESSAGE_DUPLICATE_ORDER); + } + } + + for (Order order : orders) { + model.addOrder(order); + order.setBuyer(toAdd); + } + + toAdd.addOrders(orders.stream().map(Order::getId).collect(Collectors.toList())); + model.addBuyer(toAdd); + model.switchToBuyerList(); + + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd) + + "\n" + numOrdersAdded + (numOrdersAdded == 1 ? " order" : " orders") + " added\n"); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddBuyerCommand // instanceof handles nulls + && toAdd.equals(((AddBuyerCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/addcommands/AddCommandWithPopup.java b/src/main/java/seedu/address/logic/commands/addcommands/AddCommandWithPopup.java new file mode 100644 index 00000000000..f69d601c4e6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/addcommands/AddCommandWithPopup.java @@ -0,0 +1,44 @@ +package seedu.address.logic.commands.addcommands; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Adds a person to the address book by using a pop-up window + * (currently just buyer and supplier, may extend to deliverer, order, and pet in the future). + */ +public class AddCommandWithPopup extends Command { + + public static final String COMMAND_WORD = "add"; + public static final String MESSAGE_USAGE = "Add a Buyer/Supplier with a pop-up window.\n" + + "Example: add buyer/supplier"; + public static final String MESSAGE_SUCCESS = "Pop-up window for adding %1$s initialised"; + + public static final String ADD_BUYER = "BUYER"; + public static final String ADD_SUPPLIER = "SUPPLIER"; + public static final String ADD_DELIVERER = "DELIVERER"; + + private final String typeToAdd; + + /** + * Constructs an {@code AddCommandWithPopup}. + * + * @param typeToAdd Type of person to be added. + */ + public AddCommandWithPopup(String typeToAdd) { + this.typeToAdd = typeToAdd; + } + + @Override + public CommandResult execute(Model model) { + return CommandResult.createAddByPopupCommandResult(String.format(MESSAGE_SUCCESS, typeToAdd), typeToAdd); + } + + @Override + public boolean equals(Object object) { + return (this == object) || (object instanceof AddCommandWithPopup + && ((AddCommandWithPopup) object).typeToAdd.equals(typeToAdd)); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/addcommands/AddDelivererCommand.java b/src/main/java/seedu/address/logic/commands/addcommands/AddDelivererCommand.java new file mode 100644 index 00000000000..bdf4cd6e730 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/addcommands/AddDelivererCommand.java @@ -0,0 +1,73 @@ +package seedu.address.logic.commands.addcommands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Deliverer; + +/** + * Adds a deliverer to the address book. + */ +public class AddDelivererCommand extends AddPersonCommand { + + public static final String COMMAND_WORD = "add-d"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a deliverer to the address book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_LOCATION + "LOCATION " + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_LOCATION + "China"; + + public static final String MESSAGE_SUCCESS = "New deliverer added: %1$s"; + public static final String MESSAGE_DUPLICATE_DELIVERER = "This deliverer already exists in the deliverer list"; + + private static final Logger LOGGER = LogsCenter.getLogger(AddDelivererCommand.class); + private final Deliverer toAdd; + + /** + * Creates an AddDelivererCommand to add the specified {@code Deliverer}. + */ + public AddDelivererCommand(Deliverer toAdd) { + requireNonNull(toAdd); + this.toAdd = toAdd; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasDeliverer(toAdd)) { + LOGGER.severe("Duplicate deliverer: " + toAdd.toString()); + throw new CommandException(MESSAGE_DUPLICATE_DELIVERER); + } + + model.addDeliverer(toAdd); + model.switchToDelivererList(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddDelivererCommand // instanceof handles nulls + && toAdd.equals(((AddDelivererCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/addcommands/AddOrderCommand.java b/src/main/java/seedu/address/logic/commands/addcommands/AddOrderCommand.java new file mode 100644 index 00000000000..f15355df1dc --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/addcommands/AddOrderCommand.java @@ -0,0 +1,106 @@ +package seedu.address.logic.commands.addcommands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_ADDITIONAL_REQUESTS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_PRICE_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_REQUESTS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_STATUS; + +import java.util.Collections; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.order.Order; +import seedu.address.model.person.Buyer; + +/** + * Adds an order to the contact. + */ +public class AddOrderCommand extends Command { + + public static final String COMMAND_WORD = "add-o"; + + public static final String USAGE_COMMON_PARAMETERS = + PREFIX_ORDER_STATUS + "STATUS " + + PREFIX_ORDER_REQUESTS + "REQUEST " + + PREFIX_ORDER_PRICE + "PRICE " + + PREFIX_ORDER_PRICE_RANGE + "PRICE_RANGE " + + PREFIX_ORDER_DATE + "DATE " + + "[" + PREFIX_ORDER_ADDITIONAL_REQUESTS + "ADDITIONAL_REQUEST]...\n" + + + "\nExample: " + COMMAND_WORD + " "; + public static final String USAGE_COMMON_SAMPLE_PARAMETERS = + PREFIX_ORDER_STATUS + "Pending " + + PREFIX_ORDER_REQUESTS + " (request prefixes and parameters) " + + PREFIX_ORDER_PRICE + "6.8 " + + PREFIX_ORDER_PRICE_RANGE + "5.4,8.0 " + + PREFIX_ORDER_DATE + "2022-09-30 " + + PREFIX_ORDER_ADDITIONAL_REQUESTS + "Free delivery " + + PREFIX_ORDER_ADDITIONAL_REQUESTS + "Vaccination certified"; + public static final String MESSAGE_USAGE_NEW_BUYER = COMMAND_WORD + + ": Adds an order when adding a new buyer. " + + "Parameters: " + + USAGE_COMMON_PARAMETERS + + USAGE_COMMON_SAMPLE_PARAMETERS; + public static final String MESSAGE_USAGE_EXISTING_BUYER = COMMAND_WORD + + ": Adds an order to an existing buyer. " + + "Parameters: " + + "INDEX " + + USAGE_COMMON_PARAMETERS + + "1 " + + USAGE_COMMON_SAMPLE_PARAMETERS; + + public static final String MESSAGE_DUPLICATE_ORDER = "This order already exists in the order list"; + public static final String MESSAGE_SUCCESS = "Added Order: %1$s"; + + private final Order toAdd; + private final Index index; + + /** + * Constructs a new AddOrderCommand object. + * @param toAdd The order to be added + * @param index The index of buyer that is associated with this order. + */ + public AddOrderCommand(Order toAdd, Index index) { + this.toAdd = toAdd; + this.index = index; + } + + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException If an error occurs during command execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + ObservableList lastShownList = model.getFilteredCurrList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Object o = lastShownList.get(index.getZeroBased()); + if (!(o instanceof Buyer)) { + throw new CommandException(String.format(Messages.INVALID_BUYER, index.getOneBased())); + } + + Buyer associatedBuyer = (Buyer) o; + + associatedBuyer.addOrders(Collections.singletonList(toAdd.getId())); + toAdd.setBuyer(associatedBuyer); + model.addOrder(toAdd); + model.switchToBuyerList(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/addcommands/AddPersonCommand.java b/src/main/java/seedu/address/logic/commands/addcommands/AddPersonCommand.java new file mode 100644 index 00000000000..5ea57c95e8e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/addcommands/AddPersonCommand.java @@ -0,0 +1,22 @@ +package seedu.address.logic.commands.addcommands; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Represents an abstraction of all three types of AddPersonCommand: AddBuyerCommand, AddDelivererCommand, + * and AddSupplierCommand. + */ +public abstract class AddPersonCommand extends Command { + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException If an error occurs during command execution. + */ + @Override + public abstract CommandResult execute(Model model) throws CommandException; +} diff --git a/src/main/java/seedu/address/logic/commands/addcommands/AddPetCommand.java b/src/main/java/seedu/address/logic/commands/addcommands/AddPetCommand.java new file mode 100644 index 00000000000..1509bfa7155 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/addcommands/AddPetCommand.java @@ -0,0 +1,137 @@ +package seedu.address.logic.commands.addcommands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_CERTIFICATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_COLOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_COLOR_PATTERN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_DATE_OF_BIRTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_HEIGHT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_SPECIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_VACCINATION_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_WEIGHT; + +import java.util.Collections; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; + +/** + * Adds a pet to the address book. + */ +public class AddPetCommand extends Command { + public static final String COMMAND_WORD = "add-p"; + + public static final String COMMON_PARAMETERS = + PREFIX_PET_NAME + "NAME " + + PREFIX_PET_DATE_OF_BIRTH + "DATE_OF_BIRTH " + + PREFIX_PET_COLOR + "COLOR " + + PREFIX_PET_COLOR_PATTERN + "COLOR_PATTERN " + + PREFIX_PET_HEIGHT + "HEIGHT " + + PREFIX_PET_SPECIES + "SPECIES " + + PREFIX_PET_VACCINATION_STATUS + "VACCINATION_STATUS " + + PREFIX_PET_WEIGHT + "WEIGHT " + + PREFIX_PET_PRICE + "PRICE" + + "[" + PREFIX_PET_CERTIFICATE + "CERTIFICATE]...\n" + + + "\nExample: " + COMMAND_WORD + " "; + + public static final String COMMON_SAMPLE_PARAMETERS = PREFIX_PET_NAME + "Wu Lezheng " + + PREFIX_PET_DATE_OF_BIRTH + "2001-11-20 " + + PREFIX_PET_COLOR + "red " + + PREFIX_PET_COLOR_PATTERN + "stripped " + + PREFIX_PET_HEIGHT + "39.5 " + + PREFIX_PET_SPECIES + "chihuahua " + + PREFIX_PET_VACCINATION_STATUS + "true " + + PREFIX_PET_WEIGHT + "15.3 " + + PREFIX_PET_PRICE + "20" + + PREFIX_PET_CERTIFICATE + "Good-Dog Cert. " + + PREFIX_PET_CERTIFICATE + "Royal Blood Cert. "; + + public static final String MESSAGE_USAGE_EXISTING_SUPPLIER = COMMAND_WORD + + ": Adds a new pet to an existing supplier. " + + "Parameters: " + + "INDEX " + + COMMON_PARAMETERS + + PREFIX_INDEX + "1 " + + COMMON_SAMPLE_PARAMETERS; + + public static final String MESSAGE_USAGE_NEW_SUPPLIER = COMMAND_WORD + + ": Adds a pet when adding a supplier. " + + "Parameters: " + + COMMON_PARAMETERS + + COMMON_SAMPLE_PARAMETERS; + + + public static final String MESSAGE_DUPLICATE_PET = "This pet already exists in the pet list"; + + public static final String MESSAGE_SUCCESS = "Added Pet: %1$s"; + + public static final String MESSAGE_FAILURE = "Unable to execute AddPetCommand."; + + private final Index index; + private final Pet toAdd; + + /** + * Constructs a new AddPetCommand object. + * + * @param pet The pet to be added. + * @param index The index of the associated supplier. + */ + public AddPetCommand(Pet pet, Index index) { + this.toAdd = pet; + this.index = index; + } + + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException If an error occurs during command execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + ObservableList lastShownList = model.getFilteredCurrList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Object o = lastShownList.get(index.getZeroBased()); + if (!(o instanceof Supplier)) { + throw new CommandException(String.format(Messages.INVALID_SUPPLIER, index.getOneBased())); + } + Supplier associatedSupplier = (Supplier) o; + + if (model.hasPet(toAdd)) { + throw new CommandException(AddPetCommand.MESSAGE_DUPLICATE_PET); + } + + associatedSupplier.addPets(Collections.singletonList(toAdd.getId())); + toAdd.setSupplier(associatedSupplier); + model.addPet(toAdd); + model.switchToSupplierList(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof AddPetCommand)) { + return false; + } + AddPetCommand otherCommand = (AddPetCommand) other; + return otherCommand.index.equals(this.index) && otherCommand.toAdd.isSamePet(this.toAdd); + } +} diff --git a/src/main/java/seedu/address/logic/commands/addcommands/AddSupplierCommand.java b/src/main/java/seedu/address/logic/commands/addcommands/AddSupplierCommand.java new file mode 100644 index 00000000000..b3305973e5f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/addcommands/AddSupplierCommand.java @@ -0,0 +1,97 @@ +package seedu.address.logic.commands.addcommands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; + +/** + * Adds a supplier to the address book. + */ +public class AddSupplierCommand extends AddPersonCommand { + + public static final String COMMAND_WORD = "add-s"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_LOCATION + "LOCATION " + + "\nExample: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_LOCATION + "Vietnam " + + PREFIX_PET + AddPetCommand.COMMAND_WORD + " (pet1 prefixes and parameters) " + + PREFIX_PET + AddPetCommand.COMMAND_WORD + " (pet2 prefixes and parameters) "; + + public static final String MESSAGE_SUCCESS = "New supplier added: %1$s"; + public static final String MESSAGE_DUPLICATE_SUPPLIER = "This supplier already exists in the supplier list"; + + private final Supplier toAdd; + private final List pets = new ArrayList<>(); + + /** + * Creates an AddSupplierCommand to add the specified {@code Supplier}. + */ + public AddSupplierCommand(Supplier supplier, List pets) { + requireNonNull(supplier); + toAdd = supplier; + if (pets != null) { + this.pets.addAll(pets); + } + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasSupplier(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_SUPPLIER); + } + + List pets = this.pets; + int numPetsAdded = pets.size(); + + for (Pet pet : pets) { + if (model.hasPet(pet)) { + throw new CommandException(AddPetCommand.MESSAGE_DUPLICATE_PET); + } + } + + for (Pet pet : pets) { + model.addPet(pet); + pet.setSupplier(toAdd); + } + + toAdd.addPets(pets.stream().map(Pet::getId).collect(Collectors.toList())); + model.addSupplier(toAdd); + model.switchToSupplierList(); + + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd) + + "\n" + numPetsAdded + (numPetsAdded == 1 ? " pet" : " pets") + " added\n"); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddSupplierCommand // instanceof handles nulls + && toAdd.equals(((AddSupplierCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/checkcommands/CheckBuyerCommand.java b/src/main/java/seedu/address/logic/commands/checkcommands/CheckBuyerCommand.java new file mode 100644 index 00000000000..661165a6e0c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/checkcommands/CheckBuyerCommand.java @@ -0,0 +1,57 @@ +package seedu.address.logic.commands.checkcommands; + +import static java.util.Objects.requireNonNull; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Buyer; + +/** + * Checks which Orders belong to the Buyer at the specified index. + */ +public class CheckBuyerCommand extends CheckCommand { + public static final String MESSAGE_SUCCESS = "Checking buyer %1$s"; + + /** + * Constructs a CheckBuyerCommand with index specified. + */ + public CheckBuyerCommand(Index index) { + super(index); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + ObservableList lastShownList = model.getFilteredCurrList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Object o = lastShownList.get(index.getZeroBased()); + if (!(o instanceof Buyer)) { + throw new CommandException(String.format(Messages.INVALID_BUYER, index.getOneBased())); + } + + Buyer buyer = (Buyer) o; + model.checkBuyerOrder(buyer); + return new CommandResult(String.format(MESSAGE_SUCCESS, index.getOneBased())); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (object instanceof CheckBuyerCommand) { + CheckBuyerCommand other = (CheckBuyerCommand) object; + return other.index.equals(this.index); + } + + return false; + } +} diff --git a/src/main/java/seedu/address/logic/commands/checkcommands/CheckCommand.java b/src/main/java/seedu/address/logic/commands/checkcommands/CheckCommand.java new file mode 100644 index 00000000000..27dafcd84d8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/checkcommands/CheckCommand.java @@ -0,0 +1,26 @@ +package seedu.address.logic.commands.checkcommands; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; + + +/** + * Checks a Buyer/Supplier/Deliverer/Order/Pet at the specified index. + */ +public abstract class CheckCommand extends Command { + + public static final String COMMAND_WORD = "check"; + + public static final String MESSAGE_USAGE = "Check the buyer/supplier of a specific order/pet with.\n" + + "Example: check order 1"; + + protected final Index index; + + /** + * Constructs a CheckCommand with Index. + */ + public CheckCommand(Index index) { + this.index = index; + } + +} diff --git a/src/main/java/seedu/address/logic/commands/checkcommands/CheckDelivererCommand.java b/src/main/java/seedu/address/logic/commands/checkcommands/CheckDelivererCommand.java new file mode 100644 index 00000000000..4150bc6290b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/checkcommands/CheckDelivererCommand.java @@ -0,0 +1,57 @@ +package seedu.address.logic.commands.checkcommands; + +import static java.util.Objects.requireNonNull; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Deliverer; + +/** + * Checks which Orders belong to the Deliverer at the specified index. + */ +public class CheckDelivererCommand extends CheckCommand { + public static final String MESSAGE_SUCCESS = "Checking deliverer %1$s"; + + /** + * Constructs a CheckDelivererCommand with index specified. + */ + public CheckDelivererCommand(Index index) { + super(index); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + ObservableList lastShownList = model.getFilteredCurrList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Object o = lastShownList.get(index.getZeroBased()); + if (!(o instanceof Deliverer)) { + throw new CommandException(String.format(Messages.INVALID_DELIVERER, index.getOneBased())); + } + + Deliverer deliverer = (Deliverer) o; + model.checkDelivererOrder(deliverer); + return new CommandResult(String.format(MESSAGE_SUCCESS, index.getOneBased())); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (object instanceof CheckDelivererCommand) { + CheckDelivererCommand other = (CheckDelivererCommand) object; + return other.index.equals(this.index); + } + + return false; + } +} diff --git a/src/main/java/seedu/address/logic/commands/checkcommands/CheckOrderCommand.java b/src/main/java/seedu/address/logic/commands/checkcommands/CheckOrderCommand.java new file mode 100644 index 00000000000..b03ec2adc43 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/checkcommands/CheckOrderCommand.java @@ -0,0 +1,57 @@ +package seedu.address.logic.commands.checkcommands; + +import static java.util.Objects.requireNonNull; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.order.Order; + +/** + * Checks who the Buyer is of the Order at the specified index. + */ +public class CheckOrderCommand extends CheckCommand { + public static final String MESSAGE_SUCCESS = "Checking order %1$s"; + + /** + * Constructs a CheckOrderCommand with index specified. + */ + public CheckOrderCommand(Index index) { + super(index); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + ObservableList lastShownList = model.getFilteredCurrList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Object o = lastShownList.get(index.getZeroBased()); + if (!(o instanceof Order)) { + throw new CommandException(String.format(Messages.INVALID_ORDER, index.getOneBased())); + } + + Order order = (Order) o; + model.checkBuyerOfOrder(order); + return new CommandResult(String.format(MESSAGE_SUCCESS, index.getOneBased())); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (object instanceof CheckOrderCommand) { + CheckOrderCommand other = (CheckOrderCommand) object; + return other.index.equals(this.index); + } + + return false; + } +} diff --git a/src/main/java/seedu/address/logic/commands/checkcommands/CheckPetCommand.java b/src/main/java/seedu/address/logic/commands/checkcommands/CheckPetCommand.java new file mode 100644 index 00000000000..39658270e25 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/checkcommands/CheckPetCommand.java @@ -0,0 +1,57 @@ +package seedu.address.logic.commands.checkcommands; + +import static java.util.Objects.requireNonNull; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.pet.Pet; + +/** + * Checks who the Supplier is of the Pet at the specified index. + */ +public class CheckPetCommand extends CheckCommand { + public static final String MESSAGE_SUCCESS = "Checking pet %1$s"; + + /** + * Constructs a CheckPetCommand with index specified. + */ + public CheckPetCommand(Index index) { + super(index); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + ObservableList lastShownList = model.getFilteredCurrList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Object o = lastShownList.get(index.getZeroBased()); + if (!(o instanceof Pet)) { + throw new CommandException(String.format(Messages.INVALID_PET, index.getOneBased())); + } + + Pet pet = (Pet) o; + model.checkSupplierOfPet(pet); + return new CommandResult(String.format(MESSAGE_SUCCESS, index.getOneBased())); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (object instanceof CheckPetCommand) { + CheckPetCommand other = (CheckPetCommand) object; + return other.index.equals(this.index); + } + + return false; + } +} diff --git a/src/main/java/seedu/address/logic/commands/checkcommands/CheckSupplierCommand.java b/src/main/java/seedu/address/logic/commands/checkcommands/CheckSupplierCommand.java new file mode 100644 index 00000000000..05e279e3350 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/checkcommands/CheckSupplierCommand.java @@ -0,0 +1,57 @@ +package seedu.address.logic.commands.checkcommands; + +import static java.util.Objects.requireNonNull; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Supplier; + +/** + * Checks the which Pets belong to the Supplier at the specified index. + */ +public class CheckSupplierCommand extends CheckCommand { + public static final String MESSAGE_SUCCESS = "Checking supplier %1$s"; + + /** + * Constructs a CheckSupplierCommand with index specified. + */ + public CheckSupplierCommand(Index index) { + super(index); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + ObservableList lastShownList = model.getFilteredCurrList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Object o = lastShownList.get(index.getZeroBased()); + if (!(o instanceof Supplier)) { + throw new CommandException(String.format(Messages.INVALID_SUPPLIER, index.getOneBased())); + } + + Supplier supplier = (Supplier) o; + model.checkSupplierPet(supplier); + return new CommandResult(String.format(MESSAGE_SUCCESS, index.getOneBased())); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (object instanceof CheckSupplierCommand) { + CheckSupplierCommand other = (CheckSupplierCommand) object; + return other.index.equals(this.index); + } + + return false; + } +} diff --git a/src/main/java/seedu/address/logic/commands/deletecommands/DeleteBuyerCommand.java b/src/main/java/seedu/address/logic/commands/deletecommands/DeleteBuyerCommand.java new file mode 100644 index 00000000000..1217e2c2576 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deletecommands/DeleteBuyerCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands.deletecommands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.order.Order; +import seedu.address.model.person.Buyer; + +/** + * Deletes a Buyer identified using it's displayed index from the address book. + */ +public class DeleteBuyerCommand extends DeleteCommand { + + public static final String COMMAND_WORD = "delete-b"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the buyer identified by index used in the displayed buyer list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_BUYER_SUCCESS = "Deleted Buyer: %1$s"; + + public static final String MESSAGE_DELETE_BUYER_FAILURE = "Unable to execute DeleteBuyerCommand."; + + private final Index targetIndex; + + /** + * Creates a DeleteBuyerCommand to delete the specified {@code Buyer}. + */ + public DeleteBuyerCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + ObservableList lastShownList = model.getFilteredCurrList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Object o = lastShownList.get(targetIndex.getZeroBased()); + if (!(o instanceof Buyer)) { + throw new CommandException(String.format(Messages.INVALID_BUYER, targetIndex.getOneBased())); + } + + Buyer personToDelete = (Buyer) o; + + model.deleteBuyer(personToDelete); + + List ordersFromBuyer = model.getOrdersFromBuyer(personToDelete); + + for (Order order: ordersFromBuyer) { + model.deleteOrder(order); + } + model.switchToBuyerList(); + return new CommandResult(String.format(MESSAGE_DELETE_BUYER_SUCCESS, personToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteBuyerCommand // instanceof handles nulls + && targetIndex.equals(((DeleteBuyerCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/deletecommands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/deletecommands/DeleteCommand.java new file mode 100644 index 00000000000..e17f010c784 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deletecommands/DeleteCommand.java @@ -0,0 +1,42 @@ +package seedu.address.logic.commands.deletecommands; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * The abstract base class of all DeleteCommand variations, + * including DeleteBuyerCommand, DeleteDelivererCommand, DeleteSupplierCommand, DeleteOrderCommand, DeletePetCommand. + */ +public abstract class DeleteCommand extends Command { + public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_INPUT = "delete-KEY"; + + public static final String MESSAGE_USAGE = COMMAND_INPUT + + ": Deletes the person/item identified by index used in the displayed person/item list.\n" + + "Parameter: INDEX (must be a positive integer)\n" + + "Examples:\n" + + "delete-b 1 : deletes the buyer of index 1\n" + + "delete-d 2 : deletes the deliverer of index 2\n" + + "delete-s 3 : deletes the supplier of index 3\n" + + "delete-o 1 : deletes the order of index 1\n" + + "delete-p 2 : deletes the pet of index 2"; + /** + * Creates a default base DeleteCommand. + */ + public DeleteCommand() {} + + /** + * Returns the command result to display. + * This is an abstract method that requires its subclasses, + * such as {@code DeleteBuyerCommand, DeleteDelivererCommand, DeleteSupplierCommand, DeleteOrderCommand, + * DeletePetCommand}, to implement. + * + * @param model {@code Model} which the command should operate on. + * @return CommandResult the result to be displayed. + * @throws CommandException if the command cannot work. + */ + @Override + public abstract CommandResult execute(Model model) throws CommandException; +} diff --git a/src/main/java/seedu/address/logic/commands/deletecommands/DeleteDelivererCommand.java b/src/main/java/seedu/address/logic/commands/deletecommands/DeleteDelivererCommand.java new file mode 100644 index 00000000000..524a0723ab6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deletecommands/DeleteDelivererCommand.java @@ -0,0 +1,76 @@ +package seedu.address.logic.commands.deletecommands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.order.Order; +import seedu.address.model.person.Deliverer; + + +/** + * Deletes a Deliverer identified using it's displayed index from the address book. + */ +public class DeleteDelivererCommand extends DeleteCommand { + + public static final String COMMAND_WORD = "delete-d"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the Deliverer identified by index used in the displayed Deliverer list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_DELIVERER_SUCCESS = "Deleted Deliverer: %1$s"; + + public static final String MESSAGE_DELETE_DELIVERER_FAILURE = "Unable to execute DeleteDelivererCommand."; + + private final Index targetIndex; + + /** + * Creates a DeleteDelivererCommand to delete the specified {@code Deliverer}. + */ + public DeleteDelivererCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + ObservableList lastShownList = model.getFilteredCurrList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Object o = lastShownList.get(targetIndex.getZeroBased()); + if (!(o instanceof Deliverer)) { + throw new CommandException(String.format(Messages.INVALID_DELIVERER, targetIndex.getOneBased())); + } + + Deliverer personToDelete = (Deliverer) o; + + model.deleteDeliverer(personToDelete); + + List ordersFromDeliverer = model.getOrdersFromDeliverer(personToDelete); + + for (Order order: ordersFromDeliverer) { + model.deleteOrder(order); + } + model.switchToDelivererList(); + return new CommandResult(String.format(MESSAGE_DELETE_DELIVERER_SUCCESS, personToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteDelivererCommand // instanceof handles nulls + && targetIndex.equals(((DeleteDelivererCommand) other).targetIndex)); // state check + } +} + diff --git a/src/main/java/seedu/address/logic/commands/deletecommands/DeleteOrderCommand.java b/src/main/java/seedu/address/logic/commands/deletecommands/DeleteOrderCommand.java new file mode 100644 index 00000000000..1a6297c06bf --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deletecommands/DeleteOrderCommand.java @@ -0,0 +1,66 @@ +package seedu.address.logic.commands.deletecommands; + +import static java.util.Objects.requireNonNull; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.order.Order; + +/** + * Deletes an Order identified using it's displayed index from the address book. + */ +public class DeleteOrderCommand extends DeleteCommand { + + public static final String COMMAND_WORD = "delete-o"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the Order identified by index used in the displayed Order list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_ORDER_SUCCESS = "Deleted Order: %1$s"; + + public static final String MESSAGE_DELETE_ORDER_FAILURE = "Unable to execute DeleteOrderCommand."; + + private final Index targetIndex; + + /** + * Creates a DeleteOrderCommand to delete the specified {@code Order}. + */ + public DeleteOrderCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + ObservableList lastShownList = model.getFilteredCurrList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Object o = lastShownList.get(targetIndex.getZeroBased()); + if (!(o instanceof Order)) { + throw new CommandException(String.format(Messages.INVALID_ORDER, targetIndex.getOneBased())); + } + + Order orderToDelete = (Order) o; + + model.deleteOrder(orderToDelete); + + model.switchToOrderList(); + return new CommandResult(String.format(MESSAGE_DELETE_ORDER_SUCCESS, orderToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteOrderCommand // instanceof handles nulls + && targetIndex.equals(((DeleteOrderCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/deletecommands/DeletePetCommand.java b/src/main/java/seedu/address/logic/commands/deletecommands/DeletePetCommand.java new file mode 100644 index 00000000000..a12ea830cf8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deletecommands/DeletePetCommand.java @@ -0,0 +1,67 @@ +package seedu.address.logic.commands.deletecommands; + +import static java.util.Objects.requireNonNull; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.pet.Pet; + +/** + * Deletes a Pet identified using it's displayed index from the address book. + */ +public class DeletePetCommand extends DeleteCommand { + + public static final String COMMAND_WORD = "delete-p"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the Pet identified by index used in the displayed Pet list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_PET_SUCCESS = "Deleted Pet: %1$s"; + + public static final String MESSAGE_DELETE_PET_FAILURE = "Unable to execute DeletePetCommand."; + + private final Index targetIndex; + + /** + * Creates a DeletePetCommand to delete the specified {@code Pet}. + */ + public DeletePetCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + ObservableList lastShownList = model.getFilteredCurrList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Object o = lastShownList.get(targetIndex.getZeroBased()); + if (!(o instanceof Pet)) { + throw new CommandException(String.format(Messages.INVALID_PET, targetIndex.getOneBased())); + } + + Pet petToDelete = (Pet) o; + + model.deletePet(petToDelete); + + model.switchToPetList(); + return new CommandResult(String.format(MESSAGE_DELETE_PET_SUCCESS, petToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeletePetCommand // instanceof handles nulls + && targetIndex.equals(((DeletePetCommand) other).targetIndex)); // state check + } +} + diff --git a/src/main/java/seedu/address/logic/commands/deletecommands/DeleteSupplierCommand.java b/src/main/java/seedu/address/logic/commands/deletecommands/DeleteSupplierCommand.java new file mode 100644 index 00000000000..256e5a433a2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/deletecommands/DeleteSupplierCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands.deletecommands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; + +/** + * Deletes a Supplier identified using it's displayed index from the address book. + */ +public class DeleteSupplierCommand extends DeleteCommand { + + public static final String COMMAND_WORD = "delete-s"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the Supplier identified by index used in the displayed Supplier list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_SUPPLIER_SUCCESS = "Deleted Supplier: %1$s"; + + public static final String MESSAGE_DELETE_SUPPLIER_FAILURE = "Unable to execute DeleteSupplierCommand."; + + private final Index targetIndex; + + /** + * Creates a DeleteSupplierCommand to delete the specified {@code Supplier}. + */ + public DeleteSupplierCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + ObservableList lastShownList = model.getFilteredCurrList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + Object o = lastShownList.get(targetIndex.getZeroBased()); + if (!(o instanceof Supplier)) { + throw new CommandException(String.format(Messages.INVALID_SUPPLIER, targetIndex.getOneBased())); + } + + Supplier personToDelete = (Supplier) o; + + model.deleteSupplier(personToDelete); + + List petsFromSupplier = model.getPetsFromSupplier(personToDelete); + + for (Pet pet : petsFromSupplier) { + model.deletePet(pet); + } + model.switchToSupplierList(); + return new CommandResult(String.format(MESSAGE_DELETE_SUPPLIER_SUCCESS, personToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteSupplierCommand // instanceof handles nulls + && targetIndex.equals(((DeleteSupplierCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/editcommands/EditBuyerCommand.java b/src/main/java/seedu/address/logic/commands/editcommands/EditBuyerCommand.java new file mode 100644 index 00000000000..bb1570456d4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/editcommands/EditBuyerCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands.editcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.core.index.UniqueId; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Address; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Email; +import seedu.address.model.person.Location; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; + +/** + * Edits the details of an existing Buyer. + */ +public class EditBuyerCommand extends EditCommand { + + public static final String COMMAND_WORD = "edit-b"; + + public EditBuyerCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + super(index, editPersonDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredCurrList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Object o = lastShownList.get(index.getZeroBased()); + if (!(o instanceof Buyer)) { + throw new CommandException(String.format(Messages.INVALID_BUYER, index.getOneBased())); + } + + Buyer buyerToEdit = (Buyer) o; + Buyer editedBuyer = createEditedBuyer(buyerToEdit, editPersonDescriptor); + + if (!buyerToEdit.isSamePerson(editedBuyer) && model.hasBuyer(editedBuyer)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.setBuyer(buyerToEdit, editedBuyer); + model.updateFilteredBuyerList(Model.PREDICATE_SHOW_ALL_BUYERS); + model.switchToBuyerList(); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedBuyer)); + } + + /** + * Creates and returns a {@code Buyer} with the details of {@code buyerToEdit} + * edited with {@code editPersonDescriptor}. + */ + private static Buyer createEditedBuyer(Buyer buyerToEdit, EditPersonDescriptor editPersonDescriptor) { + assert buyerToEdit != null; + Name updatedName = editPersonDescriptor.getName().orElse(buyerToEdit.getName()); + Phone updatedPhone = editPersonDescriptor.getPhone().orElse(buyerToEdit.getPhone()); + Email updatedEmail = editPersonDescriptor.getEmail().orElse(buyerToEdit.getEmail()); + Address updatedAddress = editPersonDescriptor.getAddress().orElse(buyerToEdit.getAddress()); + Location updatedLocation = editPersonDescriptor.getLocation().orElse(buyerToEdit.getLocation()); + List updateOrders = buyerToEdit.getOrderIds(); + + return new Buyer(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedLocation, updateOrders); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/editcommands/EditCommand.java similarity index 54% rename from src/main/java/seedu/address/logic/commands/EditCommand.java rename to src/main/java/seedu/address/logic/commands/editcommands/EditCommand.java index 7e36114902f..26cbe4e967b 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/editcommands/EditCommand.java @@ -1,63 +1,53 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.editcommands; import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; import java.util.Optional; -import java.util.Set; -import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import seedu.address.logic.commands.Command; import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Location; import seedu.address.model.person.Name; -import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; /** - * Edits the details of an existing person in the address book. + * Edits the details of an existing person. */ -public class EditCommand extends Command { +public abstract class EditCommand extends Command { public static final String COMMAND_WORD = "edit"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": edit-KEY. Edits the details of the person identified " + + "by the index number used in the displayed person list.\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " + + "[" + PREFIX_LOCATION + "LOCATION] \n" + + "Example: " + COMMAND_WORD + "-b" + " 1 " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com"; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; + final Index index; + final EditPersonDescriptor editPersonDescriptor; /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with + * @param index Index of the person in the filtered person list to edit. + * @param editPersonDescriptor details to edit the person with. */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { requireNonNull(index); requireNonNull(editPersonDescriptor); @@ -65,43 +55,6 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); } - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - @Override public boolean equals(Object other) { // short circuit if same object @@ -129,29 +82,28 @@ public static class EditPersonDescriptor { private Phone phone; private Email email; private Address address; - private Set tags; + private Location location; - public EditPersonDescriptor() {} + public EditPersonDescriptor() { + } /** * Copy constructor. - * A defensive copy of {@code tags} is used internally. */ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); setAddress(toCopy.address); - setTags(toCopy.tags); + setLocation(toCopy.location); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, phone, email, address, location); } - public void setName(Name name) { this.name = name; } @@ -184,21 +136,12 @@ public Optional
getAddress() { return Optional.ofNullable(address); } - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + public void setLocation(Location location) { + this.location = location; } - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + public Optional getLocation() { + return Optional.ofNullable(location); } @Override @@ -220,7 +163,7 @@ public boolean equals(Object other) { && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); + && getLocation().equals(e.getLocation()); } } } diff --git a/src/main/java/seedu/address/logic/commands/editcommands/EditDelivererCommand.java b/src/main/java/seedu/address/logic/commands/editcommands/EditDelivererCommand.java new file mode 100644 index 00000000000..d411415bcdf --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/editcommands/EditDelivererCommand.java @@ -0,0 +1,75 @@ +package seedu.address.logic.commands.editcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.core.index.UniqueId; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Address; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Email; +import seedu.address.model.person.Location; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; + +/** + * Edits the details of an existing Deliverer. + */ +public class EditDelivererCommand extends EditCommand { + + public static final String COMMAND_WORD = "edit-d"; + public EditDelivererCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + super(index, editPersonDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredCurrList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Object o = lastShownList.get(index.getZeroBased()); + if (!(o instanceof Deliverer)) { + throw new CommandException(String.format(Messages.INVALID_DELIVERER, index.getOneBased())); + } + + Deliverer delivererToEdit = (Deliverer) o; + Deliverer editedDeliverer = createEditedDeliverer(delivererToEdit, editPersonDescriptor); + + if (!delivererToEdit.isSamePerson(editedDeliverer) && model.hasDeliverer(editedDeliverer)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.setDeliverer(delivererToEdit, editedDeliverer); + model.updateFilteredDelivererList(Model.PREDICATE_SHOW_ALL_DELIVERERS); + model.switchToDelivererList(); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedDeliverer)); + } + + /** + * Creates and returns a {@code Deliverer} with the details of {@code delivererToEdit} + * edited with {@code editPersonDescriptor}. + */ + private static Deliverer createEditedDeliverer( + Deliverer delivererToEdit, EditPersonDescriptor editPersonDescriptor) { + assert delivererToEdit != null; + + Name updatedName = editPersonDescriptor.getName().orElse(delivererToEdit.getName()); + Phone updatedPhone = editPersonDescriptor.getPhone().orElse(delivererToEdit.getPhone()); + Email updatedEmail = editPersonDescriptor.getEmail().orElse(delivererToEdit.getEmail()); + Address updatedAddress = editPersonDescriptor.getAddress().orElse(delivererToEdit.getAddress()); + Location updatedLocation = editPersonDescriptor.getLocation().orElse(delivererToEdit.getLocation()); + List orders = delivererToEdit.getOrders(); + + return new Deliverer(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedLocation, orders); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/editcommands/EditSupplierCommand.java b/src/main/java/seedu/address/logic/commands/editcommands/EditSupplierCommand.java new file mode 100644 index 00000000000..608ec5b5bb5 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/editcommands/EditSupplierCommand.java @@ -0,0 +1,75 @@ +package seedu.address.logic.commands.editcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.core.index.UniqueId; +import seedu.address.logic.commands.CommandResult; +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.Location; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Supplier; + +/** + * Edits the details of an existing Supplier. + */ +public class EditSupplierCommand extends EditCommand { + + public static final String COMMAND_WORD = "edit-s"; + + public EditSupplierCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + super(index, editPersonDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredCurrList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Object o = lastShownList.get(index.getZeroBased()); + if (!(o instanceof Supplier)) { + throw new CommandException(String.format(Messages.INVALID_SUPPLIER, index.getOneBased())); + } + + Supplier supplierToEdit = (Supplier) o; + Supplier editedSupplier = createEditedSupplier(supplierToEdit, editPersonDescriptor); + + if (!supplierToEdit.isSamePerson(editedSupplier) && model.hasSupplier(editedSupplier)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.setSupplier(supplierToEdit, editedSupplier); + model.updateFilteredSupplierList(Model.PREDICATE_SHOW_ALL_SUPPLIERS); + model.switchToSupplierList(); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedSupplier)); + } + + /** + * Creates and returns a {@code Supplier} with the details of {@code supplierToEdit} + * edited with {@code editPersonDescriptor}. + */ + private static Supplier createEditedSupplier(Supplier supplierToEdit, EditPersonDescriptor editPersonDescriptor) { + assert supplierToEdit != null; + + Name updatedName = editPersonDescriptor.getName().orElse(supplierToEdit.getName()); + Phone updatedPhone = editPersonDescriptor.getPhone().orElse(supplierToEdit.getPhone()); + Email updatedEmail = editPersonDescriptor.getEmail().orElse(supplierToEdit.getEmail()); + Address updatedAddress = editPersonDescriptor.getAddress().orElse(supplierToEdit.getAddress()); + Location updatedLocation = editPersonDescriptor.getLocation().orElse(supplierToEdit.getLocation()); + List pets = supplierToEdit.getPetIds(); + + return new Supplier(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedLocation, pets); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java index a16bd14f2cd..f44c0c19186 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java @@ -1,5 +1,7 @@ package seedu.address.logic.commands.exceptions; +import seedu.address.logic.commands.Command; + /** * Represents an error which occurs during execution of a {@link Command}. */ diff --git a/src/main/java/seedu/address/logic/commands/filtercommands/FilterCommand.java b/src/main/java/seedu/address/logic/commands/filtercommands/FilterCommand.java new file mode 100644 index 00000000000..e32d097ed4d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/filtercommands/FilterCommand.java @@ -0,0 +1,44 @@ +package seedu.address.logic.commands.filtercommands; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + + +/** + * The abstract base class of all FilterCommand variations, + * including FilterOrderCommand and FilterPetCommand. + */ +public abstract class FilterCommand extends Command { + public static final String COMMAND_WORD = "filter"; + public static final String COMMAND_INPUT = "filter-KEY"; + + public static final String MESSAGE_USAGE = COMMAND_INPUT + + ": Displays items based on the specified attribute(s).\n" + + "Parameter: ITEM_TYPE PREFIX/INPUT\n" + + "Examples:\n" + + "filter-o os/Pending : filters orders that have an order status of Pending.\n" + + "filter-o os/Negotiating pr/90, 900 ar/good with children : filters orders that have an order status of " + + "Pending, price range of 90 to 900, and additional request of good with children.\n" + + "filter-p p_c/white : filters pets that have a color white. \n" + + "filter-p p_c/black p_n/doraemon p_p/50 p_s/cat p_v/true : filters pets that have a color black, " + + "the name doraemon, " + + "the species cat, and vaccination status of true.\n"; + /** + * Creates a default base DeleteCommand. + */ + public FilterCommand() {} + + /** + * Returns the command result to display. + * This is an abstract method that requires its subclasses, + * such as {@code FilterOrderCommand, FilterPetCommand}, to implement. + * + * @param model {@code Model} which the command should operate on. + * @return CommandResult the result to be displayed. + * @throws CommandException if the command cannot work. + */ + @Override + public abstract CommandResult execute(Model model) throws CommandException; +} diff --git a/src/main/java/seedu/address/logic/commands/filtercommands/FilterOrderCommand.java b/src/main/java/seedu/address/logic/commands/filtercommands/FilterOrderCommand.java new file mode 100644 index 00000000000..c6dd078fdc8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/filtercommands/FilterOrderCommand.java @@ -0,0 +1,77 @@ +package seedu.address.logic.commands.filtercommands; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.order.Order; + +/** + * Filters the attributes of an existing Order in the address book. + */ +public class FilterOrderCommand extends FilterCommand { + public static final String COMMAND_WORD = "filter-o"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters all Orders with attributes: " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: PREFIX/[KEYWORDS] PREFIX/[KEYWORDS] ...\n" + + "There are three possible attributes to filter: Additional requests, Order status, Price range \n" + + "For Additional requests, use the prefix 'o_ar' \n" + + "For Order status, use the prefix 'o_st' \n" + + "For Price range, use the prefix 'o_pr' followed by '/' and use ',' to indicate the range. " + + "Use -1 to indicate non-applicable price.\n" + + "Example: " + COMMAND_WORD + " o_ar/flufy o_st/Pending o_pr/5.5, 20.2"; + + public static final String MESSAGE_NOT_FILTERED = "At least one field to filter must be provided."; + + public static final String MESSAGE_INVALID_OS = "Order status has to be either " + + "'Pending', 'Negotiating', or 'Delivering'"; + public static final String MESSAGE_INVALID_PARAMETER = "The correct format for price range should be" + + "lower - upper \n" + + "e.g: 20- 200"; + + private final Predicate additionalRequestPredicate; + private final Predicate orderStatusPredicate; + private final Predicate priceRangePredicate; + + /** + * Creates a FilterOrderCommand to filter the specified {@code Order}. + */ + public FilterOrderCommand(Predicate additionalRequestPredicate, Predicate orderStatusPredicate, + Predicate priceRangePredicate) { + this.additionalRequestPredicate = additionalRequestPredicate; + this.orderStatusPredicate = orderStatusPredicate; + this.priceRangePredicate = priceRangePredicate; + } + + /** + * Creates a Predicate to filter the specified {@code Order}. + */ + public Predicate generatePredicate() { + return order -> additionalRequestPredicate.test(order) && orderStatusPredicate.test(order) + && priceRangePredicate.test(order); + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + Predicate finalPredicate = generatePredicate(); + model.updateFilteredOrderList(finalPredicate); + model.switchToOrderList(); + return new CommandResult( + String.format(Messages.MESSAGE_ORDERS_LISTED_OVERVIEW, model.getFilteredCurrList().size())); + } + + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterOrderCommand // instanceof handles nulls + && additionalRequestPredicate.equals(((FilterOrderCommand) other).additionalRequestPredicate) + && orderStatusPredicate.equals(((FilterOrderCommand) other).orderStatusPredicate) + && priceRangePredicate.equals(((FilterOrderCommand) other).priceRangePredicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/filtercommands/FilterPetCommand.java b/src/main/java/seedu/address/logic/commands/filtercommands/FilterPetCommand.java new file mode 100644 index 00000000000..9ea442bbfeb --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/filtercommands/FilterPetCommand.java @@ -0,0 +1,82 @@ +package seedu.address.logic.commands.filtercommands; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.parser.filtercommandparser.FilterPetCommandParser; +import seedu.address.model.Model; +import seedu.address.model.pet.Pet; + +/** + * Filters and lists all pets in address book whose attributes match the argument keywords. + * Keyword matching is case-insensitive. + */ +public class FilterPetCommand extends FilterCommand { + public static final String COMMAND_WORD = "filter-p"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters all pets with attributes: " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: PREFIX/[KEYWORDS] PREFIX/[KEYWORDS] ...\n" + + "There are five possible attributes to filter: Color, Name, Price, Species, Vaccination status \n" + + "For Color, use the prefix " + FilterPetCommandParser.COLOR_PREFIX + "\n" + + "For Name, use the prefix " + FilterPetCommandParser.PET_NAME_PREFIX + "\n" + + "For Price, use the prefix " + FilterPetCommandParser.PRICE_PREFIX + "\n" + + "For Species, use the prefix " + FilterPetCommandParser.SPECIES_PREFIX + "\n" + + "For Vaccination Status, use the prefix " + FilterPetCommandParser.VACCINATION_PREFIX + "\n" + + "Example: " + COMMAND_WORD + " p_n/Ashy p_s/Cat"; + + private final Predicate colorPredicate; + private final Predicate namePredicate; + private final Predicate pricePredicate; + private final Predicate speciesPredicate; + private final Predicate vaccinationPredicate; + + /** + * Creates a FilterPetCommand to filter the specified {@code Pet}. + */ + public FilterPetCommand(Predicate cPredicate, Predicate nPredicate, Predicate pPredicate, + Predicate sPredicate, Predicate vaccinationPredicate) { + this.colorPredicate = cPredicate; + this.namePredicate = nPredicate; + this.pricePredicate = pPredicate; + this.speciesPredicate = sPredicate; + this.vaccinationPredicate = vaccinationPredicate; + } + + /** + * Creates a Predicate to filter the specified {@code Pet}. + */ + public Predicate generatePredicate() { + return new Predicate() { + @Override + public boolean test(Pet pet) { + return colorPredicate.test(pet) && namePredicate.test(pet) && pricePredicate.test(pet) + && speciesPredicate.test(pet) && vaccinationPredicate.test(pet); + } + }; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + Predicate finalPredicate = generatePredicate(); + model.updateFilteredPetList(finalPredicate); + model.switchToPetList(); + return new CommandResult( + String.format(Messages.MESSAGE_PETS_LISTED_OVERVIEW, model.getFilteredCurrList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterPetCommand // instanceof handles nulls + && colorPredicate.equals(((FilterPetCommand) other).colorPredicate) + && namePredicate.equals(((FilterPetCommand) other).namePredicate) + && pricePredicate.equals(((FilterPetCommand) other).pricePredicate) + && speciesPredicate.equals(((FilterPetCommand) other).speciesPredicate) + && vaccinationPredicate.equals(((FilterPetCommand) other).vaccinationPredicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/findcommands/FindBuyerCommand.java b/src/main/java/seedu/address/logic/commands/findcommands/FindBuyerCommand.java new file mode 100644 index 00000000000..8647c1d9adb --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/findcommands/FindBuyerCommand.java @@ -0,0 +1,43 @@ +package seedu.address.logic.commands.findcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.PersonCategory; + +/** + * Finds and lists all Buyers in address book whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. + */ +public class FindBuyerCommand extends FindCommand { + /** + * Constructs a FindBuyerCommand, which has three predicates. + * Keyword matching is case insensitive. + * + * @param bPredicate A Predicate for Buyers. + */ + public FindBuyerCommand(Predicate bPredicate) { + super(bPredicate, null, null, PersonCategory.BUYER); + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredBuyerList(getBuyerPredicate()); + model.switchToBuyerList(); + return new CommandResult(String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, + model.getFilteredBuyerList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindBuyerCommand // instanceof handles nulls + && getBuyerPredicate().equals(((FindBuyerCommand) other).getBuyerPredicate())); + } +} diff --git a/src/main/java/seedu/address/logic/commands/findcommands/FindCommand.java b/src/main/java/seedu/address/logic/commands/findcommands/FindCommand.java new file mode 100644 index 00000000000..5e0bdc5de79 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/findcommands/FindCommand.java @@ -0,0 +1,93 @@ +package seedu.address.logic.commands.findcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.PersonCategory; +import seedu.address.model.person.Supplier; + +/** + * Finds and lists all Persons in address book whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. + */ +public class FindCommand extends Command { + + public static final String COMMAND_WORD = "find"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds buyers, deliverers or suppliers " + + "who match the specified attribute. " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: PREFIX/[KEYWORDS]...\n" + + "For Address, use the prefix 'a' \n" + + "For Email, use the prefix 'e' \n" + + "For Location, use the prefix 'l' \n" + + "For Name, use the prefix 'n' \n" + + "For Phone, use the prefix 'ph' \n" + + "Example: " + COMMAND_WORD + " n/Bernice"; + + private final Predicate buyerPredicate; + + private final Predicate delivererPredicate; + + private final Predicate supplierPredicate; + + private final PersonCategory type; + + /** + * Constructs a FindCommand, which has three predicates - one + * for Buyers, one for Deliverers and one for Suppliers. + * Keyword matching is case insensitive. + * + * @param bPredicate A Predicate for Buyers. + * @param dPredicate A Predicate for Deliverers. + * @param sPredicate A Predicate for Suppliers. + * @param type Whether to return a CommandResult relevant to the + * Buyer, Deliverer or Supplier. + */ + public FindCommand(Predicate bPredicate, Predicate dPredicate, + Predicate sPredicate, PersonCategory type) { + this.buyerPredicate = bPredicate; + this.delivererPredicate = dPredicate; + this.supplierPredicate = sPredicate; + this.type = type; + } + + public Predicate getBuyerPredicate() { + return buyerPredicate; + } + + public Predicate getDelivererPredicate() { + return delivererPredicate; + } + + public Predicate getSupplierPredicate() { + return supplierPredicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredBuyerList(buyerPredicate); + model.updateFilteredDelivererList(delivererPredicate); + model.updateFilteredSupplierList(supplierPredicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredMainList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindCommand // instanceof handles nulls + && buyerPredicate.equals(((FindCommand) other).buyerPredicate) // state checck + && delivererPredicate.equals(((FindCommand) other).delivererPredicate) + && supplierPredicate.equals(((FindCommand) other).supplierPredicate) + && type.equals(((FindCommand) other).type)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/findcommands/FindDelivererCommand.java b/src/main/java/seedu/address/logic/commands/findcommands/FindDelivererCommand.java new file mode 100644 index 00000000000..4d1a2f7e9cd --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/findcommands/FindDelivererCommand.java @@ -0,0 +1,44 @@ +package seedu.address.logic.commands.findcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.PersonCategory; + +/** + * Finds and lists all Deliverers in address book whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. + */ +public class FindDelivererCommand extends FindCommand { + + /** + * Constructs a FindDelivererCommand, which has three predicates. + * Keyword matching is case insensitive. + * + * @param dPredicate A Predicate for Deliverers. + */ + public FindDelivererCommand(Predicate dPredicate) { + super(null, dPredicate, null, PersonCategory.DELIVERER); + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredDelivererList(getDelivererPredicate()); + model.switchToDelivererList(); + return new CommandResult(String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, + model.getFilteredDelivererList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindDelivererCommand // instanceof handles nulls + && getDelivererPredicate().equals(((FindDelivererCommand) other).getDelivererPredicate())); + } +} diff --git a/src/main/java/seedu/address/logic/commands/findcommands/FindSupplierCommand.java b/src/main/java/seedu/address/logic/commands/findcommands/FindSupplierCommand.java new file mode 100644 index 00000000000..30bc5ca235a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/findcommands/FindSupplierCommand.java @@ -0,0 +1,44 @@ +package seedu.address.logic.commands.findcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.person.PersonCategory; +import seedu.address.model.person.Supplier; + +/** + * Finds and lists all Suppliers in address book whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. + */ +public class FindSupplierCommand extends FindCommand { + + /** + * Constructs a FindSupplierCommand, which has three predicates. + * Keyword matching is case insensitive. + * + * @param sPredicate A Predicate for Suppliers. + */ + public FindSupplierCommand(Predicate sPredicate) { + super(null, null, sPredicate, PersonCategory.SUPPLIER); + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredSupplierList(getSupplierPredicate()); + model.switchToSupplierList(); + return new CommandResult(String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, + model.getFilteredSupplierList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindSupplierCommand // instanceof handles nulls + && getSupplierPredicate().equals(((FindSupplierCommand) other).getSupplierPredicate())); + } +} diff --git a/src/main/java/seedu/address/logic/commands/listcommands/ListAllCommand.java b/src/main/java/seedu/address/logic/commands/listcommands/ListAllCommand.java new file mode 100644 index 00000000000..e5df5c54fdd --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/listcommands/ListAllCommand.java @@ -0,0 +1,27 @@ +package seedu.address.logic.commands.listcommands; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all Persons to the user. + */ +public class ListAllCommand extends ListCommand { + public static final String MESSAGE_SUCCESS = "Listed all"; + + @Override + public CommandResult execute(Model model) { + updateFilteredList(model); + model.switchToMainList(); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + return object instanceof ListAllCommand; + } +} diff --git a/src/main/java/seedu/address/logic/commands/listcommands/ListBuyerCommand.java b/src/main/java/seedu/address/logic/commands/listcommands/ListBuyerCommand.java new file mode 100644 index 00000000000..accff99a268 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/listcommands/ListBuyerCommand.java @@ -0,0 +1,28 @@ +package seedu.address.logic.commands.listcommands; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all Buyers to the user. + */ +public class ListBuyerCommand extends ListCommand { + + public static final String MESSAGE_SUCCESS = "Listed all buyers"; + + @Override + public CommandResult execute(Model model) { + updateFilteredList(model); + model.switchToBuyerList(); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + return object instanceof ListBuyerCommand; + } +} diff --git a/src/main/java/seedu/address/logic/commands/listcommands/ListCommand.java b/src/main/java/seedu/address/logic/commands/listcommands/ListCommand.java new file mode 100644 index 00000000000..dfa04777495 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/listcommands/ListCommand.java @@ -0,0 +1,30 @@ +package seedu.address.logic.commands.listcommands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.Command; +import seedu.address.model.Model; + +/** + * Lists all items to the user. + */ +public abstract class ListCommand extends Command { + + public static final String COMMAND_WORD = "list"; + + public static final String MESSAGE_USAGE = "List all the Buyer/Supplier/Deliverer/Order/Pet.\n" + + "Example: list all/buyer/supplier/deliverer/order/pet"; + + /** + * Removes the predicates on all the lists. + */ + public void updateFilteredList(Model model) { + requireNonNull(model); + model.updateFilteredBuyerList(Model.PREDICATE_SHOW_ALL_BUYERS); + model.updateFilteredSupplierList(Model.PREDICATE_SHOW_ALL_SUPPLIERS); + model.updateFilteredDelivererList(Model.PREDICATE_SHOW_ALL_DELIVERERS); + model.updateFilteredPetList(Model.PREDICATE_SHOW_ALL_PETS); + model.updateFilteredOrderList(Model.PREDICATE_SHOW_ALL_ORDERS); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/listcommands/ListDelivererCommand.java b/src/main/java/seedu/address/logic/commands/listcommands/ListDelivererCommand.java new file mode 100644 index 00000000000..d84ec871ff8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/listcommands/ListDelivererCommand.java @@ -0,0 +1,28 @@ +package seedu.address.logic.commands.listcommands; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all Deliverers to the user. + */ +public class ListDelivererCommand extends ListCommand { + + public static final String MESSAGE_SUCCESS = "Listed all deliverers"; + + @Override + public CommandResult execute(Model model) { + updateFilteredList(model); + model.switchToDelivererList(); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + return object instanceof ListDelivererCommand; + } +} diff --git a/src/main/java/seedu/address/logic/commands/listcommands/ListOrderCommand.java b/src/main/java/seedu/address/logic/commands/listcommands/ListOrderCommand.java new file mode 100644 index 00000000000..2952a405fb2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/listcommands/ListOrderCommand.java @@ -0,0 +1,27 @@ +package seedu.address.logic.commands.listcommands; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all Orders to the user. + */ +public class ListOrderCommand extends ListCommand { + public static final String MESSAGE_SUCCESS = "Listed all orders"; + + @Override + public CommandResult execute(Model model) { + updateFilteredList(model); + model.switchToOrderList(); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + return object instanceof ListOrderCommand; + } +} diff --git a/src/main/java/seedu/address/logic/commands/listcommands/ListPetCommand.java b/src/main/java/seedu/address/logic/commands/listcommands/ListPetCommand.java new file mode 100644 index 00000000000..b60bf4993a7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/listcommands/ListPetCommand.java @@ -0,0 +1,27 @@ +package seedu.address.logic.commands.listcommands; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all Pets to the user. + */ +public class ListPetCommand extends ListCommand { + public static final String MESSAGE_SUCCESS = "Listed all pets"; + + @Override + public CommandResult execute(Model model) { + updateFilteredList(model); + model.switchToPetList(); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + return object instanceof ListPetCommand; + } +} diff --git a/src/main/java/seedu/address/logic/commands/listcommands/ListSupplierCommand.java b/src/main/java/seedu/address/logic/commands/listcommands/ListSupplierCommand.java new file mode 100644 index 00000000000..cf21ca3d12a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/listcommands/ListSupplierCommand.java @@ -0,0 +1,28 @@ +package seedu.address.logic.commands.listcommands; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; + +/** + * Lists all Suppliers to the user. + */ +public class ListSupplierCommand extends ListCommand { + + public static final String MESSAGE_SUCCESS = "Listed all suppliers"; + + @Override + public CommandResult execute(Model model) { + updateFilteredList(model); + model.switchToSupplierList(); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + return object instanceof ListSupplierCommand; + } +} diff --git a/src/main/java/seedu/address/logic/commands/sortcommands/SortBuyerCommand.java b/src/main/java/seedu/address/logic/commands/sortcommands/SortBuyerCommand.java new file mode 100644 index 00000000000..0122b7d9433 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/sortcommands/SortBuyerCommand.java @@ -0,0 +1,42 @@ +package seedu.address.logic.commands.sortcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Buyer; + +/** + * Sorts the Buyers' list according to the specified / default attribute. + */ +public class SortBuyerCommand extends SortCommand { + + public static final String MESSAGE_SUCCESS = + "buyer list has been sorted successfully"; + public static final String MESSAGE_USAGE = + "Acceptable buyer attributes are order, address, email, location, name, phone"; + public static final String MESSAGE_WRONG_ATTRIBUTE = + "%1$s is not a supported attribute in sorting buyer list \n%2$s"; + + private final Comparator comparator; + + /** + * Constructs a SortBuyerCommand with specified comparator. + * @param comparator The specified comparator. + */ + public SortBuyerCommand(Comparator comparator) { + requireNonNull(comparator); + + this.comparator = comparator; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + model.sortBuyer(comparator); + model.switchToBuyerList(); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/sortcommands/SortCommand.java b/src/main/java/seedu/address/logic/commands/sortcommands/SortCommand.java new file mode 100644 index 00000000000..cd1af8e6bbe --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/sortcommands/SortCommand.java @@ -0,0 +1,24 @@ +package seedu.address.logic.commands.sortcommands; + +import seedu.address.logic.commands.Command; + +/** + * The abstract base class of all SortCommand variations, + * including SortBuyerCommand, SortDelivererCommand, SortSupplierCommand, SortOrderCommand, SortPetCommand. + */ +public abstract class SortCommand extends Command { + + public static final String COMMAND_WORD = "sort"; + public static final String MESSAGE_UNKNOWN_LIST = "%1$s is not an valid list type\n%2$s"; + public static final String MESSAGE_SUPPORTED_LIST = "The following lists are supported: \n" + + "buyer, deliverer, supplier, order, pet"; + public static final String MESSAGE_ONLY_ALPHABET_PARAMETERS = + "Please enter alphabets only for attributes, %1$s is not recognised"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sorts the specified list according to the specified attribute.\n" + + "Parameters: LIST_TYPE [ATTRIBUTES...]\n" + + "Examples:\n" + + COMMAND_WORD + " buyer\n" + + COMMAND_WORD + " pet price height weight\n"; + +} diff --git a/src/main/java/seedu/address/logic/commands/sortcommands/SortDelivererCommand.java b/src/main/java/seedu/address/logic/commands/sortcommands/SortDelivererCommand.java new file mode 100644 index 00000000000..575aa282fc6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/sortcommands/SortDelivererCommand.java @@ -0,0 +1,42 @@ +package seedu.address.logic.commands.sortcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Deliverer; + +/** + * Sorts the Deliverers' list according to the specified /default attribute. + */ +public class SortDelivererCommand extends SortCommand { + + public static final String MESSAGE_SUCCESS = + "deliverer list has been sorted successfully"; + public static final String MESSAGE_WRONG_ATTRIBUTE = + "%1$s is not a supported attribute in sorting deliverer list\n%2$s"; + public static final String MESSAGE_USAGE = + "Acceptable deliverer attributes are pet, address, email, location, name, phone"; + + private final Comparator comparator; + + /** + * Constructs a SortDelivererCommand with the specified comparator. + * @param comparator The specified comparator. + */ + public SortDelivererCommand(Comparator comparator) { + requireNonNull(comparator); + this.comparator = comparator; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + model.sortDeliverer(comparator); + model.switchToDelivererList(); + return new CommandResult(MESSAGE_SUCCESS); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/sortcommands/SortOrderCommand.java b/src/main/java/seedu/address/logic/commands/sortcommands/SortOrderCommand.java new file mode 100644 index 00000000000..f6299e19f3c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/sortcommands/SortOrderCommand.java @@ -0,0 +1,42 @@ +package seedu.address.logic.commands.sortcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.order.Order; + +/** + * Sorts the Orders' list according to the specified / default attribute. + */ +public class SortOrderCommand extends SortCommand { + + public static final String MESSAGE_SUCCESS = "order list has been sorted successfully"; + public static final String MESSAGE_USAGE = + "Acceptable order attributes are pricerange, duedate, price, orderstatus"; + public static final String MESSAGE_WRONG_ATTRIBUTE = + "%1$s is not a supported attribute in sorting order list \n%2$s"; + + private final Comparator comparator; + + /** + * Constructs a SortOrderCommand with specified comparator. + * @param comparator The specified comparator. + */ + public SortOrderCommand(Comparator comparator) { + requireNonNull(comparator); + + this.comparator = comparator; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + model.sortOrder(comparator); + model.switchToOrderList(); + return new CommandResult(MESSAGE_SUCCESS); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/sortcommands/SortPetCommand.java b/src/main/java/seedu/address/logic/commands/sortcommands/SortPetCommand.java new file mode 100644 index 00000000000..e35b3877f06 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/sortcommands/SortPetCommand.java @@ -0,0 +1,40 @@ +package seedu.address.logic.commands.sortcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.pet.Pet; + +/** + * Sorts the Pets' list. + */ +public class SortPetCommand extends SortCommand { + + public static final String MESSAGE_SUCCESS = "pet list has been sorted successfully"; + public static final String MESSAGE_WRONG_ATTRIBUTE = + "%1$s is not a supported attribute in sorting pet list \n%2$s"; + public static final String MESSAGE_USAGE = + "Acceptable order attributes are name, color, colorpattern, birthdate, species, price"; + + private final Comparator comparator; + + /** + * Constructs a SortPetCommand with specified comparator. + * @param comparator The specified comparator. + */ + public SortPetCommand(Comparator comparator) { + requireNonNull(comparator); + this.comparator = comparator; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + model.sortPet(comparator); + model.switchToPetList(); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/sortcommands/SortSupplierCommand.java b/src/main/java/seedu/address/logic/commands/sortcommands/SortSupplierCommand.java new file mode 100644 index 00000000000..2ab90f42338 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/sortcommands/SortSupplierCommand.java @@ -0,0 +1,41 @@ +package seedu.address.logic.commands.sortcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; + +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Supplier; + +/** + * Sorts the Suppliers' list. + */ +public class SortSupplierCommand extends SortCommand { + + public static final String MESSAGE_SUCCESS = + "supplier list has been sorted successfully"; + public static final String MESSAGE_WRONG_ATTRIBUTE = + "%1$s is not a supported attribute in sorting supplier list \n%2$s"; + public static final String MESSAGE_USAGE = + "Acceptable supplier attributes are order, address, email, location, name, phone"; + + private final Comparator comparator; + + /** + * Constructs a SortSupplierCommand with specified comparator. + * @param comparator The specified comparator. + */ + public SortSupplierCommand(Comparator comparator) { + requireNonNull(comparator); + this.comparator = comparator; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + model.sortSupplier(comparator); + model.switchToSupplierList(); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/util/CommandUtil.java b/src/main/java/seedu/address/logic/commands/util/CommandUtil.java new file mode 100644 index 00000000000..72687d1dce8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/util/CommandUtil.java @@ -0,0 +1,64 @@ +package seedu.address.logic.commands.util; + +/** + * Provides utils for some Command classes. + */ +public class CommandUtil { + public static final String[] ACCEPTABLE_BUYER_PARAMETER = new String[]{"BUYERS", "BUYER", "B"}; + + public static final String[] ACCEPTABLE_DELIVERER_PARAMETER = new String[]{ + "DELIVERERS", "DELIVERER", "D"}; + + public static final String[] ACCEPTABLE_SUPPLIER_PARAMETER = new String[]{"SUPPLIERS", "SUPPLIER", "S"}; + + public static final String[] ACCEPTABLE_ORDER_PARAMETER = new String[]{"ORDERS", "ORDER", "O"}; + + public static final String[] ACCEPTABLE_PET_PARAMETER = new String[]{"PETS", "PET", "P"}; + public static final String[] ACCEPTABLE_ALL_PARAMETER = new String[]{"ALL", "A"}; + + public static final String[] ACCEPTABLE_SORT_ORDER_SIZE_PARAMETER = new String[]{ + "ORDERS", "ORDER"}; + + public static final String[] ACCEPTABLE_SORT_PET_LIST_SIZE_PARAMETER = new String[]{"PETS", "PET"}; + + public static final String[] ACCEPTABLE_SORT_ADDRESS_PARAMETER = new String[]{"ADDRESS", "ADDR", "A"}; + + public static final String[] ACCEPTABLE_SORT_EMAIL_PARAMETER = new String[]{"EMAIL", "EMA", "EM", "E"}; + + public static final String[] ACCEPTABLE_SORT_NAME_PARAMETER = new String[]{"NAME", "NA", "N"}; + + public static final String[] ACCEPTABLE_SORT_LOCATION_PARAMETER = new String[]{"LOCATION", "LOC", "L"}; + + public static final String[] ACCEPTABLE_SORT_PHONE_PARAMETER = new String[]{"PHONE", "PH"}; + + public static final String[] ACCEPTABLE_SORT_PRICE_RANGE_PARAMETER = new String[]{"PRICERANGE", "PRANGE", "PRICER", + "PR"}; + public static final String[] ACCEPTABLE_SORT_DUE_DATE_PARAMETER = new String[]{"DUEDATE", "DUE", "BY", "DATE", + "BYDATE", "D"}; + public static final String[] ACCEPTABLE_SORT_PRICE_PARAMETER = new String[]{"PRICE", "P"}; + public static final String[] ACCEPTABLE_SORT_STATUS_PARAMETER = new String[]{"ORDERSTATUS", "STATUS", "OS", "S"}; + public static final String[] ACCEPTABLE_SORT_COLOR_PARAMETER = new String[]{"COLOR", "C"}; + public static final String[] ACCEPTABLE_SORT_COLOR_PATTERN_PARAMETER = new String[]{"COLORPATTERN", "CPATTERN", + "COLORP", "CP"}; + public static final String[] ACCEPTABLE_SORT_BIRTH_DATE_PARAMETER = new String[]{"BIRTHDATE", "BDATE", "BIRTHD", + "DATE", "BD"}; + public static final String[] ACCEPTABLE_SORT_SPECIES_PARAMETER = new String[]{"SPECIES", "S"}; + public static final String[] ACCEPTABLE_SORT_HEIGHT_PARAMETER = new String[]{"HEIGHT", "H"}; + public static final String[] ACCEPTABLE_SORT_WEIGHT_PARAMETER = new String[]{"WEIGHT", "W"}; + + /** + * Checks if a given parameter matches any parameter in a given acceptable parameters array. + * @param acceptableParameters The given parameter array. + * @param parameter The given parameter. + * @return The boolean value. + */ + public static boolean isValidParameter(String[] acceptableParameters, String parameter) { + parameter = parameter.toUpperCase(); + for (String para : acceptableParameters) { + if (parameter.equals(para)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e8..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser 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 AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..ea7fd746076 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -6,16 +6,55 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.MatchCommand; +import seedu.address.logic.commands.addcommands.AddBuyerCommand; +import seedu.address.logic.commands.addcommands.AddCommandWithPopup; +import seedu.address.logic.commands.addcommands.AddDelivererCommand; +import seedu.address.logic.commands.addcommands.AddOrderCommand; +import seedu.address.logic.commands.addcommands.AddPetCommand; +import seedu.address.logic.commands.addcommands.AddSupplierCommand; +import seedu.address.logic.commands.checkcommands.CheckCommand; +import seedu.address.logic.commands.deletecommands.DeleteBuyerCommand; +import seedu.address.logic.commands.deletecommands.DeleteCommand; +import seedu.address.logic.commands.deletecommands.DeleteDelivererCommand; +import seedu.address.logic.commands.deletecommands.DeleteOrderCommand; +import seedu.address.logic.commands.deletecommands.DeletePetCommand; +import seedu.address.logic.commands.deletecommands.DeleteSupplierCommand; +import seedu.address.logic.commands.editcommands.EditBuyerCommand; +import seedu.address.logic.commands.editcommands.EditCommand; +import seedu.address.logic.commands.editcommands.EditDelivererCommand; +import seedu.address.logic.commands.editcommands.EditSupplierCommand; +import seedu.address.logic.commands.filtercommands.FilterCommand; +import seedu.address.logic.commands.filtercommands.FilterOrderCommand; +import seedu.address.logic.commands.filtercommands.FilterPetCommand; +import seedu.address.logic.commands.findcommands.FindCommand; +import seedu.address.logic.commands.listcommands.ListCommand; +import seedu.address.logic.commands.sortcommands.SortCommand; +import seedu.address.logic.parser.addcommandparser.AddBuyerCommandParser; +import seedu.address.logic.parser.addcommandparser.AddCommandWithPopupParser; +import seedu.address.logic.parser.addcommandparser.AddDelivererCommandParser; +import seedu.address.logic.parser.addcommandparser.AddOrderCommandParser; +import seedu.address.logic.parser.addcommandparser.AddPetCommandParser; +import seedu.address.logic.parser.addcommandparser.AddSupplierCommandParser; +import seedu.address.logic.parser.deletecommandparser.DeleteBuyerCommandParser; +import seedu.address.logic.parser.deletecommandparser.DeleteDelivererCommandParser; +import seedu.address.logic.parser.deletecommandparser.DeleteOrderCommandParser; +import seedu.address.logic.parser.deletecommandparser.DeletePetCommandParser; +import seedu.address.logic.parser.deletecommandparser.DeleteSupplierCommandParser; +import seedu.address.logic.parser.editcommandparser.EditBuyerCommandParser; +import seedu.address.logic.parser.editcommandparser.EditDelivererCommandParser; +import seedu.address.logic.parser.editcommandparser.EditSupplierCommandParser; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.filtercommandparser.FilterOrderCommandParser; +import seedu.address.logic.parser.filtercommandparser.FilterPetCommandParser; +import seedu.address.logic.parser.findcommandparser.FindBuyerCommandParser; +import seedu.address.logic.parser.findcommandparser.FindCommandParser; +import seedu.address.logic.parser.findcommandparser.FindDelivererCommandParser; +import seedu.address.logic.parser.findcommandparser.FindSupplierCommandParser; /** * Parses user input. @@ -44,23 +83,77 @@ public Command parseCommand(String userInput) throws ParseException { final String arguments = matcher.group("arguments"); switch (commandWord) { - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); + case AddBuyerCommand.COMMAND_WORD: + return new AddBuyerCommandParser().parse(arguments); - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); + case AddDelivererCommand.COMMAND_WORD: + return new AddDelivererCommandParser().parse(arguments); + + case AddOrderCommand.COMMAND_WORD: + return new AddOrderCommandParser().parse(arguments); + + case AddPetCommand.COMMAND_WORD: + return new AddPetCommandParser().parse(arguments); + + case AddSupplierCommand.COMMAND_WORD: + return new AddSupplierCommandParser().parse(arguments); case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + + case DeleteBuyerCommand.COMMAND_WORD: + return new DeleteBuyerCommandParser().parse(arguments); + + case DeleteDelivererCommand.COMMAND_WORD: + return new DeleteDelivererCommandParser().parse(arguments); + + case DeleteSupplierCommand.COMMAND_WORD: + return new DeleteSupplierCommandParser().parse(arguments); + + case DeleteOrderCommand.COMMAND_WORD: + return new DeleteOrderCommandParser().parse(arguments); + + case DeletePetCommand.COMMAND_WORD: + return new DeletePetCommandParser().parse(arguments); + + case EditCommand.COMMAND_WORD: + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + + case EditBuyerCommand.COMMAND_WORD: + return new EditBuyerCommandParser().parse(arguments); + + case EditDelivererCommand.COMMAND_WORD: + return new EditDelivererCommandParser().parse(arguments); + + case EditSupplierCommand.COMMAND_WORD: + return new EditSupplierCommandParser().parse(arguments); case ClearCommand.COMMAND_WORD: return new ClearCommand(); + case FindBuyerCommandParser.PARSE_WORD: + return new FindBuyerCommandParser().parse(arguments); + + case FindDelivererCommandParser.PARSE_WORD: + return new FindDelivererCommandParser().parse(arguments); + + case FindSupplierCommandParser.PARSE_WORD: + return new FindSupplierCommandParser().parse(arguments); + case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case SortCommand.COMMAND_WORD: + return new SortCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: - return new ListCommand(); + return new ListCommandParser().parse(arguments); + + case AddCommandWithPopup.COMMAND_WORD: + return new AddCommandWithPopupParser().parse(arguments); + + case CheckCommand.COMMAND_WORD: + return new CheckCommandParser().parse(arguments); case ExitCommand.COMMAND_WORD: return new ExitCommand(); @@ -68,6 +161,18 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case FilterCommand.COMMAND_WORD: + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE)); + + case FilterPetCommand.COMMAND_WORD: + return new FilterPetCommandParser().parse(arguments); + + case FilterOrderCommand.COMMAND_WORD: + return new FilterOrderCommandParser().parse(arguments); + + case MatchCommand.COMMAND_WORD: + return new MatchCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/CheckCommandParser.java b/src/main/java/seedu/address/logic/parser/CheckCommandParser.java new file mode 100644 index 00000000000..d43a9ff1c55 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CheckCommandParser.java @@ -0,0 +1,59 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.checkcommands.CheckBuyerCommand; +import seedu.address.logic.commands.checkcommands.CheckCommand; +import seedu.address.logic.commands.checkcommands.CheckDelivererCommand; +import seedu.address.logic.commands.checkcommands.CheckOrderCommand; +import seedu.address.logic.commands.checkcommands.CheckPetCommand; +import seedu.address.logic.commands.checkcommands.CheckSupplierCommand; +import seedu.address.logic.commands.util.CommandUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a subclass of the {@code CheckCommand}. + */ +public class CheckCommandParser implements Parser { + + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?.\\S*)(?.\\d*)"); + + @Override + public CheckCommand parse(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CheckCommand.MESSAGE_USAGE)); + } + + final String checkType = matcher.group("checkType"); + final String indexStr = matcher.group("index"); + Index index = ParserUtil.parseIndex(indexStr); + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_BUYER_PARAMETER, checkType)) { + return new CheckBuyerCommand(index); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SUPPLIER_PARAMETER, checkType)) { + return new CheckSupplierCommand(index); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_DELIVERER_PARAMETER, checkType)) { + return new CheckDelivererCommand(index); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_ORDER_PARAMETER, checkType)) { + return new CheckOrderCommand(index); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_PET_PARAMETER, checkType)) { + return new CheckPetCommand(index); + } + + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CheckCommand.MESSAGE_USAGE)); + + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..5d3a94e4d52 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -7,9 +7,36 @@ public class CliSyntax { /* Prefix definitions */ public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); + public static final Prefix PREFIX_PHONE = new Prefix("ph/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_PERSON_CATEGORY = new Prefix("c/"); + public static final Prefix PREFIX_INDEX = new Prefix("i/"); + public static final Prefix PREFIX_ORDER = new Prefix("o/"); + public static final Prefix PREFIX_LOCATION = new Prefix("l/"); + // Prefixes for orders + public static final Prefix PREFIX_ORDER_STATUS = new Prefix("o_st/"); + public static final Prefix PREFIX_ORDER_REQUESTS = new Prefix("o_r/"); + public static final Prefix PREFIX_ORDER_PRICE = new Prefix("o_p/"); + public static final Prefix PREFIX_ORDER_PRICE_RANGE = new Prefix("o_pr/"); + public static final Prefix PREFIX_ORDER_ADDITIONAL_REQUESTS = new Prefix("o_ar/"); + public static final Prefix PREFIX_ORDER_DATE = new Prefix("o_d/"); + public static final Prefix PREFIX_ORDER_AGE = new Prefix("o_a/"); + public static final Prefix PREFIX_ORDER_SPECIES = new Prefix("o_sp/"); + public static final Prefix PREFIX_ORDER_COLOR = new Prefix("o_c/"); + public static final Prefix PREFIX_ORDER_COLOR_PATTERN = new Prefix("o_cp/"); + + // Prefixes for pets + public static final Prefix PREFIX_PET = new Prefix("p/"); + public static final Prefix PREFIX_PET_NAME = new Prefix("p_n/"); + public static final Prefix PREFIX_PET_DATE_OF_BIRTH = new Prefix("p_d/"); + public static final Prefix PREFIX_PET_COLOR = new Prefix("p_c/"); + public static final Prefix PREFIX_PET_COLOR_PATTERN = new Prefix("p_cp/"); + public static final Prefix PREFIX_PET_HEIGHT = new Prefix("p_h/"); + public static final Prefix PREFIX_PET_CERTIFICATE = new Prefix("p_cert/"); + public static final Prefix PREFIX_PET_SPECIES = new Prefix("p_s/"); + public static final Prefix PREFIX_PET_VACCINATION_STATUS = new Prefix("p_v/"); + public static final Prefix PREFIX_PET_WEIGHT = new Prefix("p_w/"); + public static final Prefix PREFIX_PET_PRICE = new Prefix("p_p/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 522b93081cc..00000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index 4fb71f23103..00000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ListCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCommandParser.java new file mode 100644 index 00000000000..cc1d2cd29eb --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java @@ -0,0 +1,50 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.listcommands.ListAllCommand; +import seedu.address.logic.commands.listcommands.ListBuyerCommand; +import seedu.address.logic.commands.listcommands.ListCommand; +import seedu.address.logic.commands.listcommands.ListDelivererCommand; +import seedu.address.logic.commands.listcommands.ListOrderCommand; +import seedu.address.logic.commands.listcommands.ListPetCommand; +import seedu.address.logic.commands.listcommands.ListSupplierCommand; +import seedu.address.logic.commands.util.CommandUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a subclass of the {@code ListCommand}.. + */ +public class ListCommandParser implements Parser { + @Override + public ListCommand parse(String userInput) throws ParseException { + userInput = userInput.trim().toUpperCase(); + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_BUYER_PARAMETER, userInput)) { + return new ListBuyerCommand(); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SUPPLIER_PARAMETER, userInput)) { + return new ListSupplierCommand(); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_DELIVERER_PARAMETER, userInput)) { + return new ListDelivererCommand(); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_ORDER_PARAMETER, userInput)) { + return new ListOrderCommand(); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_PET_PARAMETER, userInput)) { + return new ListPetCommand(); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_ALL_PARAMETER, userInput)) { + return new ListAllCommand(); + } + + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + + } +} diff --git a/src/main/java/seedu/address/logic/parser/MatchCommandParser.java b/src/main/java/seedu/address/logic/parser/MatchCommandParser.java new file mode 100644 index 00000000000..75260fb24e5 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/MatchCommandParser.java @@ -0,0 +1,43 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MISSING_INDEX; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.MatchCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses user input to create a {@code MatchCommand}. + */ +public class MatchCommandParser implements Parser { + + /** + * Constructs a MatchCommandParser. + */ + public MatchCommandParser() { + } + + /** + * Parses {@code userInput} into a command and returns it. + * + * @param userInput The string input by the user + * @throws ParseException if {@code userInput} does not conform the expected format. + */ + @Override + public MatchCommand parse(String userInput) throws ParseException { + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(userInput); + + String preamble = argMultimap.getPreamble(); + if (preamble.isBlank()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + MESSAGE_MISSING_INDEX + MatchCommand.MESSAGE_USAGE)); + } + + String indexStr = preamble.split(" ")[0]; + Index index = ParserUtil.parseIndex(indexStr); + + return new MatchCommand(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..5cc97aaf926 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -1,36 +1,109 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_ADDITIONAL_REQUESTS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_AGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_COLOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_COLOR_PATTERN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_PRICE_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_REQUESTS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_SPECIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_CERTIFICATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_COLOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_COLOR_PATTERN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_DATE_OF_BIRTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_HEIGHT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_SPECIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_VACCINATION_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_WEIGHT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.model.ModelManager.ACCEPTABLE_DATE_FORMATS; +import static seedu.address.model.ModelManager.PREFERRED_DATE_FORMAT; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.logging.Logger; +import java.util.stream.Stream; +import seedu.address.commons.core.LogsCenter; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; +import seedu.address.logic.commands.addcommands.AddBuyerCommand; +import seedu.address.logic.commands.addcommands.AddDelivererCommand; +import seedu.address.logic.commands.addcommands.AddOrderCommand; +import seedu.address.logic.commands.addcommands.AddPersonCommand; +import seedu.address.logic.commands.addcommands.AddPetCommand; +import seedu.address.logic.commands.addcommands.AddSupplierCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.AdditionalRequests; +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderStatus; +import seedu.address.model.order.Price; +import seedu.address.model.order.PriceRange; +import seedu.address.model.order.Request; import seedu.address.model.person.Address; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; import seedu.address.model.person.Email; +import seedu.address.model.person.Location; import seedu.address.model.person.Name; +import seedu.address.model.person.PersonCategory; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Age; +import seedu.address.model.pet.Color; +import seedu.address.model.pet.ColorPattern; +import seedu.address.model.pet.DateOfBirth; +import seedu.address.model.pet.Height; +import seedu.address.model.pet.Pet; +import seedu.address.model.pet.PetCertificate; +import seedu.address.model.pet.PetName; +import seedu.address.model.pet.Species; +import seedu.address.model.pet.VaccinationStatus; +import seedu.address.model.pet.Weight; /** * Contains utility methods used for parsing strings in the various *Parser classes. */ public class ParserUtil { - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + /* + * The first character of the additional request must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + private static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + private static final Logger logger = LogsCenter.getLogger(ParserUtil.class); /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { + logger.fine("Parsing index: " + oneBasedIndex); String trimmedIndex = oneBasedIndex.trim(); if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, Index.MESSAGE_USAGE)); } return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } @@ -43,6 +116,7 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { */ public static Name parseName(String name) throws ParseException { requireNonNull(name); + logger.fine("Parsing name: " + name); String trimmedName = name.trim(); if (!Name.isValidName(trimmedName)) { throw new ParseException(Name.MESSAGE_CONSTRAINTS); @@ -58,6 +132,7 @@ public static Name parseName(String name) throws ParseException { */ public static Phone parsePhone(String phone) throws ParseException { requireNonNull(phone); + logger.fine("Parsing phone: " + phone); String trimmedPhone = phone.trim(); if (!Phone.isValidPhone(trimmedPhone)) { throw new ParseException(Phone.MESSAGE_CONSTRAINTS); @@ -73,6 +148,7 @@ public static Phone parsePhone(String phone) throws ParseException { */ public static Address parseAddress(String address) throws ParseException { requireNonNull(address); + logger.fine("Parsing address: " + address); String trimmedAddress = address.trim(); if (!Address.isValidAddress(trimmedAddress)) { throw new ParseException(Address.MESSAGE_CONSTRAINTS); @@ -88,6 +164,7 @@ public static Address parseAddress(String address) throws ParseException { */ public static Email parseEmail(String email) throws ParseException { requireNonNull(email); + logger.fine("Parsing email: " + email); String trimmedEmail = email.trim(); if (!Email.isValidEmail(trimmedEmail)) { throw new ParseException(Email.MESSAGE_CONSTRAINTS); @@ -96,29 +173,579 @@ public static Email parseEmail(String email) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String location} into an {@code Location}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code location} is invalid. + */ + public static Location parseLocation(String location) throws ParseException { + requireNonNull(location); + logger.fine("Parsing location: " + location); + String trimmedLocation = location.trim(); + if (!isInputAlphanumericAndNotBlank(trimmedLocation)) { + throw new ParseException(Location.MESSAGE_CONSTRAINTS); + } + return new Location(trimmedLocation); + } + + /** + * Parses a {@code String args} into an {@code Person (Buyer, Deliverer, or Supplier}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code orderString} is invalid. + */ + public static AddPersonCommand parseAddPersonCommand(String args, PersonCategory category) throws ParseException { + requireAllNonNull(args, category); + + logger.fine("Parsing person: " + args + " category: " + category); + + ArgumentMultimap argMultimap = getPersonAttributes(args, category); + + Name name = parseName(argMultimap.getValue(PREFIX_NAME).orElse("")); + Phone phone = parsePhone(argMultimap.getValue(PREFIX_PHONE).orElse("")); + Email email = parseEmail(argMultimap.getValue(PREFIX_EMAIL).orElse("")); + Address address = parseAddress(argMultimap.getValue(PREFIX_ADDRESS).orElse("")); + Location location = parseLocation(argMultimap.getValue(PREFIX_LOCATION).orElse("")); + + switch (category) { + case BUYER: + Buyer buyer = new Buyer(name, phone, email, address, location, null); + List orders = parseOrders(argMultimap.getAllValues(PREFIX_ORDER), false); + return new AddBuyerCommand(buyer, orders); + + case DELIVERER: + Deliverer deliverer = new Deliverer(name, phone, email, address, location, null); + return new AddDelivererCommand(deliverer); + + case SUPPLIER: + Supplier supplier = new Supplier(name, phone, email, address, location, null); + List pets = parsePets(argMultimap.getAllValues(PREFIX_PET), false); + return new AddSupplierCommand(supplier, pets); + + default: + // There are only three enum constants + break; + } + return null; + } + + /** + * Parses a {@code String orderString} into an {@code Order}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code orderString} is invalid. + */ + public static Order parseOrder(String orderString, boolean isBuyerExisting) throws ParseException { + requireNonNull(orderString); + logger.fine("Parsing order: " + orderString); + String trimmedOrderString = orderString.trim(); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(trimmedOrderString, + PREFIX_ORDER_STATUS, + PREFIX_ORDER_REQUESTS, + PREFIX_ORDER_PRICE, + PREFIX_ORDER_PRICE_RANGE, + PREFIX_ORDER_ADDITIONAL_REQUESTS, + PREFIX_ORDER_DATE); + if (!arePrefixesPresent(argMultimap, + PREFIX_ORDER_STATUS, + PREFIX_ORDER_REQUESTS, + PREFIX_ORDER_PRICE, + PREFIX_ORDER_PRICE_RANGE, + PREFIX_ORDER_DATE)) { + throw isBuyerExisting + ? new ParseException(AddOrderCommand.MESSAGE_USAGE_EXISTING_BUYER) + : new ParseException(AddOrderCommand.MESSAGE_USAGE_NEW_BUYER); + } + + PriceRange priceRange = parsePriceRange(argMultimap.getValue(PREFIX_ORDER_PRICE_RANGE).orElse("")); + Request request = parseRequest(argMultimap.getValue(PREFIX_ORDER_REQUESTS).orElse("")); + AdditionalRequests additionalRequests = + parseAdditionalRequests(argMultimap.getAllValues(PREFIX_ORDER_ADDITIONAL_REQUESTS)); + LocalDate byDate = parseDate(argMultimap.getValue(PREFIX_ORDER_DATE).orElse("")); + Price price = parsePrice(argMultimap.getValue(PREFIX_ORDER_PRICE).orElse("")); + OrderStatus orderStatus = parseOrderStatus(argMultimap.getValue(PREFIX_ORDER_STATUS).orElse("")); + + if (priceRange.comparePrice(price) != PriceRange.WITHIN_RANGE) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, PriceRange.MESSAGE_CONSTRAINT)); + } + + return new Order(null, priceRange, request, additionalRequests, byDate, price, orderStatus); + } + + /** + * Parses {@code Collection orders} into a {@code List}. + */ + public static List parseOrders(Collection orders, boolean isBuyerExisting) throws ParseException { + requireNonNull(orders); + final List orderList = new ArrayList<>(); + for (String order : orders) { + orderList.add(parseOrder(order, isBuyerExisting)); + } + return orderList; + } + + /** + * Parses {@code Collection pets} into a {@code List}. + */ + public static List parsePets(Collection pets, boolean isSupplierExisting) throws ParseException { + requireNonNull(pets); + final List petList = new ArrayList<>(); + for (String pet : pets) { + petList.add(parsePet(pet, isSupplierExisting)); + } + return petList; + } + + /** + * Parses a {@code String name} into a {@code PetName}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code name} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + public static PetName parsePetName(String name) throws ParseException { + requireNonNull(name); + logger.fine("Parsing pet name: " + name); + String trimmedName = name.trim(); + if (!PetName.isValidName(trimmedName)) { + throw new ParseException(PetName.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + return new PetName(trimmedName); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a {@code String orderStatus} into an {@code OrderStatus}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code orderStatus} is invalid. + */ + public static OrderStatus parseOrderStatus(String orderStatus) throws ParseException { + requireNonNull(orderStatus); + logger.fine("Parsing order status: " + orderStatus); + String trimmedOrderStatus = orderStatus.trim(); + if (!OrderStatus.isValidOrderStatus(trimmedOrderStatus)) { + throw new ParseException(OrderStatus.MESSAGE_CONSTRAINTS); + } + return Arrays + .stream(OrderStatus.class.getEnumConstants()) + .filter(x -> x.toString().equals(trimmedOrderStatus)) + .findFirst() + .orElse(OrderStatus.PENDING); + } + + /** + * Parses a {@code String request} into an {@code Request}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code request} is invalid. + */ + public static Request parseRequest(String request) throws ParseException { + requireNonNull(request); + logger.fine("Parsing request: " + request); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(request, + PREFIX_ORDER_SPECIES, + PREFIX_ORDER_AGE, + PREFIX_ORDER_COLOR, + PREFIX_ORDER_COLOR_PATTERN); + if (!arePrefixesPresent(argMultimap, + PREFIX_ORDER_SPECIES, + PREFIX_ORDER_AGE, + PREFIX_ORDER_COLOR, + PREFIX_ORDER_COLOR_PATTERN)) { + throw new ParseException(Request.MESSAGE_USAGE); + } + + Age age = parseAge(argMultimap.getValue(PREFIX_ORDER_AGE).orElse("")); + Color color = parseColor(argMultimap.getValue(PREFIX_ORDER_COLOR).orElse("")); + ColorPattern colorPattern = parseColorPattern(argMultimap.getValue(PREFIX_ORDER_COLOR_PATTERN).orElse("")); + Species species = parseSpecies(argMultimap.getValue(PREFIX_ORDER_SPECIES).orElse("")); + return new Request(age, color, colorPattern, species); + } + + /** + * Parses a {@code String petString} into an {@code Pet}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code petString} is invalid. + */ + public static Pet parsePet(String petString, boolean isSupplierExisting) throws ParseException { + + requireNonNull(petString); + + logger.fine("Parsing pet: " + petString); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(petString, + PREFIX_PET_NAME, + PREFIX_PET_DATE_OF_BIRTH, + PREFIX_PET_COLOR, + PREFIX_PET_COLOR_PATTERN, + PREFIX_PET_HEIGHT, + PREFIX_PET_CERTIFICATE, + PREFIX_PET_SPECIES, + PREFIX_PET_VACCINATION_STATUS, + PREFIX_PET_PRICE, + PREFIX_PET_WEIGHT); + if (!arePrefixesPresent(argMultimap, + PREFIX_PET_NAME, + PREFIX_PET_DATE_OF_BIRTH, + PREFIX_PET_COLOR, + PREFIX_PET_COLOR_PATTERN, + PREFIX_PET_HEIGHT, + PREFIX_PET_SPECIES, + PREFIX_PET_PRICE, + PREFIX_PET_VACCINATION_STATUS, + PREFIX_PET_WEIGHT)) { + throw isSupplierExisting + ? new ParseException(AddPetCommand.MESSAGE_USAGE_EXISTING_SUPPLIER) + : new ParseException(AddPetCommand.MESSAGE_USAGE_NEW_SUPPLIER); + } + + PetName name = parsePetName(argMultimap.getValue(PREFIX_PET_NAME).orElse("")); + DateOfBirth dateOfBirth = parseDateOfBirth(argMultimap.getValue(PREFIX_PET_DATE_OF_BIRTH).orElse("")); + Color color = parseColor(argMultimap.getValue(PREFIX_PET_COLOR).orElse("")); + ColorPattern colorPattern = parseColorPattern(argMultimap.getValue(PREFIX_PET_COLOR_PATTERN).orElse("")); + Height height = parseHeight(argMultimap.getValue(PREFIX_PET_HEIGHT).orElse("")); + Set certificates = parseCertificates(argMultimap.getAllValues(PREFIX_PET_CERTIFICATE)); + Species species = parseSpecies(argMultimap.getValue(PREFIX_PET_SPECIES).orElse("")); + Weight weight = parseWeight(argMultimap.getValue(PREFIX_PET_WEIGHT).orElse("")); + Price price = parsePrice(argMultimap.getValue(PREFIX_PET_PRICE).orElse("")); + VaccinationStatus vaccinationStatus = + parseVaccinationStatus(argMultimap.getValue(PREFIX_PET_VACCINATION_STATUS).orElse("false")); + + return new Pet(name, + null, + color, + colorPattern, + dateOfBirth, + species, + weight, + height, + vaccinationStatus, + price, + certificates); + } + + /** + * Parses a {@code String price} into an {@code Price}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code price} is invalid. + */ + public static Price parsePrice(String price) throws ParseException { + requireNonNull(price); + logger.fine("Parsing price: " + price); + String trimmedPrice = price.trim(); + + double doublePrice; + try { + doublePrice = Double.parseDouble(trimmedPrice); + } catch (NumberFormatException ex) { + throw new ParseException(Price.MESSAGE_USAGE); + } + + Price toReturn = new Price(doublePrice); + if ((!toReturn.isNotApplicablePrice()) && toReturn.compareTo(new Price(0)) < 0) { + throw new ParseException(Price.MESSAGE_USAGE); + } + return toReturn; + } + + /** + * Parses a {@code String priceRange} into an {@code PriceRange}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code priceRange} is invalid. + */ + public static PriceRange parsePriceRange(String priceRange) throws ParseException { + requireNonNull(priceRange); + logger.fine("Parsing price range: " + priceRange); + String[] splitPrices = priceRange.split(PriceRange.DELIMITER); + if (splitPrices.length != 2) { + throw new ParseException(PriceRange.MESSAGE_USAGE); + } + + Price lower = parsePrice(splitPrices[0]); + Price upper = parsePrice(splitPrices[1]); + + if ((!upper.isNotApplicablePrice()) && upper.compareTo(lower) < 0) { + throw new ParseException(PriceRange.MESSAGE_USAGE); + } + + return new PriceRange(lower, upper); + } + + /** + * Parses {@code Collection requests} into a {@code AdditionalRequests}. + * + * @throws ParseException if the given {@code request} is invalid. */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); + public static AdditionalRequests parseAdditionalRequests(Collection requests) throws ParseException { + requireNonNull(requests); + String[] parameters = new String[requests.size()]; + int index = 0; + for (String request: requests) { + logger.fine("Parsing additional request: " + request); + String trimmedRequest = request.trim(); + if (!isInputAlphanumericAndNotBlank(trimmedRequest)) { + throw new ParseException(AdditionalRequests.MESSAGE_CONSTRAINTS); + } + parameters[index] = trimmedRequest; + index++; + } + return new AdditionalRequests(parameters); + } + + /** + * Parses a {@code String date} into an {@code LocalDate}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code date} cannot be parsed in all acceptable formats. + */ + public static LocalDate parseDate(String date) throws ParseException { + logger.fine("Parsing date: " + date); + LocalDate output; + for (String format: ACCEPTABLE_DATE_FORMATS) { + try { + output = LocalDate.parse(date, DateTimeFormatter.ofPattern(format)); + return output; + } catch (DateTimeParseException exception) { + //Do nothing because it will eventually throw an exception if no formats match + } + } + throw new ParseException("The date should be in this format: " + PREFERRED_DATE_FORMAT + + ". And also check any out-of-range date."); + } + + /** + * Parses a {@code String age} into an {@code Age}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code age} is invalid. + */ + public static Age parseAge(String age) throws ParseException { + requireNonNull(age); + logger.fine("Parsing age: " + age); + int intAge; + try { + intAge = Integer.parseInt(age); + } catch (NumberFormatException ex) { + throw new ParseException(Age.MESSAGE_USAGE); + } + if (intAge < 0) { + throw new ParseException(Age.MESSAGE_USAGE); + } + return new Age(intAge); + } + + /** + * Parses a {@code String color} into an {@code Color}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code color} is invalid. + */ + public static Color parseColor(String color) throws ParseException { + requireNonNull(color); + logger.fine("Parsing color: " + color); + String trimmedColor = color.trim(); + if (!isInputAlphanumericAndNotBlank(trimmedColor)) { + throw new ParseException(Color.MESSAGE_CONSTRAINTS); + } + return new Color(trimmedColor); + } + + /** + * Parses a {@code String colorPattern} into an {@code colorPattern}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code colorPattern} is invalid. + */ + public static ColorPattern parseColorPattern(String colorPattern) throws ParseException { + requireNonNull(colorPattern); + logger.fine("Parsing color pattern: " + colorPattern); + String trimmedColorPattern = colorPattern.trim(); + if (!isInputAlphanumericAndNotBlank(trimmedColorPattern)) { + throw new ParseException(ColorPattern.MESSAGE_CONSTRAINTS); + } + return new ColorPattern(trimmedColorPattern); + } + + /** + * Parses a {@code String species} into an {@code species}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code species} is invalid. + */ + public static Species parseSpecies(String species) throws ParseException { + requireNonNull(species); + logger.fine("Parsing species: " + species); + String trimmedSpecies = species.trim(); + if (!isInputAlphanumericAndNotBlank(trimmedSpecies)) { + throw new ParseException(Species.MESSAGE_CONSTRAINTS); + } + return new Species(trimmedSpecies); + } + + /** + * Parses a {@code String birthday} into an {@code DateOfBirth}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code birthday} cannot be parsed in all acceptable formats. + */ + public static DateOfBirth parseDateOfBirth(String date) throws ParseException { + LocalDate output; + logger.fine("Parsing date of birth: " + date); + for (String format: ACCEPTABLE_DATE_FORMATS) { + try { + output = LocalDate.parse(date, DateTimeFormatter.ofPattern(format)); + if (output.compareTo(LocalDate.now()) > 0) { + throw new ParseException(DateOfBirth.MESSAGE_USAGE); + } + return new DateOfBirth(output); + } catch (DateTimeParseException exception) { + //Do nothing because it will eventually throw an exception if no formats match + } + } + throw new ParseException(DateOfBirth.MESSAGE_USAGE); + } + + /** + * Parses a {@code String height} into an {@code Height}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code height} is invalid. + */ + public static Height parseHeight(String height) throws ParseException { + requireNonNull(height); + logger.fine("Parsing height: " + height); + double doubleHeight; + try { + doubleHeight = Double.parseDouble(height); + } catch (NumberFormatException ex) { + throw new ParseException(Height.MESSAGE_USAGE); + } + if (doubleHeight < 0) { + throw new ParseException(Height.MESSAGE_USAGE); + } + + return new Height(doubleHeight); + } + + /** + * Parses {@code Collection certificates} into a {@code Set}. + * + * @throws ParseException if some of the given {@code certificates} are invalid. + */ + public static Set parseCertificates(Collection certificates) throws ParseException { + requireNonNull(certificates); + final Set certificateSet = new HashSet<>(); + for (String cert : certificates) { + certificateSet.add(parsePetCertificate(cert)); + } + return certificateSet; + } + + /** + * Parses a {@code String petCertificate} into an {@code PetCertificate}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code certificate} is invalid. + */ + public static PetCertificate parsePetCertificate(String certificate) throws ParseException { + requireNonNull(certificate); + String trimmedCertificate = certificate.trim(); + if (!isInputAlphanumericAndNotBlank(trimmedCertificate)) { + throw new ParseException(PetCertificate.MESSAGE_CONSTRAINTS); + } + return new PetCertificate(trimmedCertificate); + } + + /** + * Parses a {@code String vaccinationStatus} into an {@code VaccinationStatus}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code weight} is invalid. + */ + public static VaccinationStatus parseVaccinationStatus(String vaccinationStatus) throws ParseException { + requireNonNull(vaccinationStatus); + logger.fine("Parsing vac status: " + vaccinationStatus); + if ("true".equals(vaccinationStatus)) { + return new VaccinationStatus(true); + } else if ("false".equals(vaccinationStatus)) { + return new VaccinationStatus(false); + } else { + throw new ParseException(VaccinationStatus.MESSAGE_USAGE); + } + } + + /** + * Parses a {@code String weight} into an {@code Weight}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code weight} is invalid. + */ + public static Weight parseWeight(String weight) throws ParseException { + requireNonNull(weight); + logger.fine("Parsing weight: " + weight); + double doubleWeight; + try { + doubleWeight = Double.parseDouble(weight); + } catch (NumberFormatException ex) { + throw new ParseException(Weight.MESSAGE_USAGE); + } + if (doubleWeight < 0) { + throw new ParseException(Weight.MESSAGE_USAGE); + } + + return new Weight(doubleWeight); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + public static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + private static boolean isInputAlphanumericAndNotBlank(String test) { + return (!test.isBlank()) && test.matches(VALIDATION_REGEX); + } + + private static ArgumentMultimap getPersonAttributes(String args, PersonCategory category) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, + PREFIX_NAME, + PREFIX_PHONE, + PREFIX_EMAIL, + PREFIX_ADDRESS, + PREFIX_LOCATION, + PREFIX_ORDER, + PREFIX_PET); + + if (!arePrefixesPresent(argMultimap, + PREFIX_NAME, + PREFIX_ADDRESS, + PREFIX_PHONE, + PREFIX_EMAIL, + PREFIX_LOCATION) || !argMultimap.getPreamble().isEmpty()) { + switch (category) { + case BUYER: + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddBuyerCommand.MESSAGE_USAGE)); + + case DELIVERER: + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddDelivererCommand.MESSAGE_USAGE)); + + case SUPPLIER: + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddSupplierCommand.MESSAGE_USAGE)); + + default: + // There are only three enum constants + break; + } } - return tagSet; + return argMultimap; } } diff --git a/src/main/java/seedu/address/logic/parser/PredicateParser.java b/src/main/java/seedu/address/logic/parser/PredicateParser.java new file mode 100644 index 00000000000..07f29cc060f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/PredicateParser.java @@ -0,0 +1,261 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.filtercommands.FilterOrderCommand; +import seedu.address.logic.commands.filtercommands.FilterPetCommand; +import seedu.address.logic.commands.findcommands.FindCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderStatus; +import seedu.address.model.order.Price; +import seedu.address.model.order.predicates.AdditionalRequestPredicate; +import seedu.address.model.order.predicates.OrderStatusPredicate; +import seedu.address.model.order.predicates.PriceRangePredicate; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Supplier; +import seedu.address.model.person.predicates.AddressContainsKeywordsPredicate; +import seedu.address.model.person.predicates.EmailContainsKeywordsPredicate; +import seedu.address.model.person.predicates.LocationContainsKeywordsPredicate; +import seedu.address.model.person.predicates.NameContainsKeywordsPredicate; +import seedu.address.model.person.predicates.PhoneContainsKeywordsPredicate; +import seedu.address.model.pet.Pet; +import seedu.address.model.pet.predicates.ColorContainsKeywordsPredicate; +import seedu.address.model.pet.predicates.PetNameContainsKeywordsPredicate; +import seedu.address.model.pet.predicates.PriceContainsKeywordsPredicate; +import seedu.address.model.pet.predicates.SpeciesContainsKeywordsPredicate; +import seedu.address.model.pet.predicates.VaccinationStatusPredicate; + +/** + * Parses input arguments and creates a new Predicate. + */ +public class PredicateParser { + //For persons + private static final String ADDRESS_PREFIX = "a"; + private static final String EMAIL_PREFIX = "e"; + private static final String LOC_PREFIX = "l"; + private static final String NAME_PREFIX = "n"; + private static final String PHONE_PREFIX = "ph"; + + //For pets + private static final String COLOR_PREFIX = "p_c/"; + private static final String PET_NAME_PREFIX = "p_n/"; + private static final String PRICE_PREFIX = "p_p/"; + private static final String SPECIES_PREFIX = "p_s/"; + private static final String VACCINATION_PREFIX = "p_v/"; + + //For orders + private static final String ADDITIONAL_REQUEST_PREFIX = "o_ar/"; + private static final String ORDER_STATUS_PREFIX = "o_st/"; + private static final String PRICE_RANGE_PREFIX = "o_pr/"; + + /** + * Parses the given {@code String} of arguments in the context of a Predicate + * and returns a Predicate. + * @throws ParseException if the user input does not conform the expected format. + */ + public static Predicate parseBuyer(String input) throws ParseException { + String[] nameKeywords = input.trim().split("/"); + if (nameKeywords.length < 2 || nameKeywords[1].isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + String query = nameKeywords[1].trim(); + switch (nameKeywords[0]) { + case ADDRESS_PREFIX: + return new AddressContainsKeywordsPredicate<>(Arrays.asList(query.split("\\s+"))); + case EMAIL_PREFIX: + if (query.split("\\s+").length != 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + return new EmailContainsKeywordsPredicate<>(Arrays.asList(query)); + case LOC_PREFIX: + return new LocationContainsKeywordsPredicate<>(Arrays.asList(query.split("\\s+"))); + case NAME_PREFIX: + return new NameContainsKeywordsPredicate<>(Arrays.asList(query.split("\\s+"))); + case PHONE_PREFIX: + if (query.split("\\s+").length != 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + return new PhoneContainsKeywordsPredicate<>(Arrays.asList(query)); + default: + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + } + + /** + * Parses the given {@code String} of arguments in the context of a Predicate + * and returns a Predicate. + * @throws ParseException if the user input does not conform the expected format. + */ + public static Predicate parseDeliverer(String input) throws ParseException { + String[] nameKeywords = input.trim().split("/"); + if (nameKeywords.length < 2 || nameKeywords[1].isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + String query = nameKeywords[1].trim(); + switch (nameKeywords[0]) { + case ADDRESS_PREFIX: + return new AddressContainsKeywordsPredicate<>(Arrays.asList(query.split("\\s+"))); + case EMAIL_PREFIX: + if (query.split("\\s+").length != 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + return new EmailContainsKeywordsPredicate<>(Arrays.asList(query)); + case LOC_PREFIX: + return new LocationContainsKeywordsPredicate<>(Arrays.asList(query.split("\\s+"))); + case NAME_PREFIX: + return new NameContainsKeywordsPredicate<>(Arrays.asList(query.split("\\s+"))); + case PHONE_PREFIX: + if (query.split("\\s+").length != 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + return new PhoneContainsKeywordsPredicate<>(Arrays.asList(query)); + default: + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + } + + /** + * Parses the given {@code String} of arguments in the context of a Predicate + * and returns a Predicate. + * @throws ParseException if the user input does not conform the expected format. + */ + public static Predicate parseSupplier(String input) throws ParseException { + String[] nameKeywords = input.trim().split("/"); + if (nameKeywords.length < 2 || nameKeywords[1].isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + String query = nameKeywords[1].trim(); + switch (nameKeywords[0]) { + case ADDRESS_PREFIX: + return new AddressContainsKeywordsPredicate<>(Arrays.asList(query.split("\\s+"))); + case EMAIL_PREFIX: + if (query.split("\\s+").length != 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + return new EmailContainsKeywordsPredicate<>(Arrays.asList(query)); + case LOC_PREFIX: + return new LocationContainsKeywordsPredicate<>(Arrays.asList(query.split("\\s+"))); + case NAME_PREFIX: + return new NameContainsKeywordsPredicate<>(Arrays.asList(query.split("\\s+"))); + case PHONE_PREFIX: + if (query.split("\\s+").length != 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + return new PhoneContainsKeywordsPredicate<>(Arrays.asList(query)); + default: + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + } + + /** + * Parses the given {@code String} of arguments in the context of a Predicate + * and returns a Predicate. + * @throws ParseException if the user input does not conform the expected format. + */ + public static Predicate parsePet(String input, String prefix) throws ParseException { + + input = input.trim(); + + if (input.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterPetCommand.MESSAGE_USAGE)); + } + String[] inputs = input.split("\\s+"); + switch (prefix) { + case COLOR_PREFIX: + return new ColorContainsKeywordsPredicate<>(Arrays.asList(inputs)); + case PET_NAME_PREFIX: + return new PetNameContainsKeywordsPredicate<>(Arrays.asList(inputs)); + case PRICE_PREFIX: + if (!input.matches("[0-9][0-9.]*[0-9]")) { + throw new ParseException("Input has to be numbers without spaces"); + } + + if (inputs.length > 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterPetCommand.MESSAGE_USAGE)); + } + return new PriceContainsKeywordsPredicate<>(Arrays.asList(Double.parseDouble(input))); + case SPECIES_PREFIX: + return new SpeciesContainsKeywordsPredicate<>(Arrays.asList(inputs)); + case VACCINATION_PREFIX: + input = input.trim(); + if (!input.equals(Boolean.toString(true)) && !input.equals(Boolean.toString(false))) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterPetCommand.MESSAGE_USAGE)); + } + return new VaccinationStatusPredicate<>(Boolean.parseBoolean(input)); + default: + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterPetCommand.MESSAGE_USAGE)); + } + } + + /** + * Parses the given {@code String} of arguments in the context of a Predicate + * and returns a Predicate. + * @throws ParseException if the user input does not conform the expected format. + */ + public static Predicate parseOrder(String input, String prefix) throws ParseException { + input = input.trim(); + + if (input.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterOrderCommand.MESSAGE_USAGE)); + } + + switch (prefix) { + case ADDITIONAL_REQUEST_PREFIX: + return new AdditionalRequestPredicate<>(Arrays.asList(input)); + case ORDER_STATUS_PREFIX: + input = input.toUpperCase(); + if (input.equals(OrderStatus.DELIVERING.toString().toUpperCase())) { + return new OrderStatusPredicate<>(OrderStatus.DELIVERING); + } else if (input.equals(OrderStatus.NEGOTIATING.toString().toUpperCase())) { + return new OrderStatusPredicate<>(OrderStatus.NEGOTIATING); + } else if (input.equals(OrderStatus.PENDING.toString().toUpperCase())) { + return new OrderStatusPredicate<>(OrderStatus.PENDING); + } + throw new ParseException(FilterOrderCommand.MESSAGE_INVALID_OS); + case PRICE_RANGE_PREFIX: + final Pattern format = Pattern.compile("(?.?\\S+),(?.?\\S+)"); + final Matcher matcher = format.matcher(input); + if (!matcher.matches()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterOrderCommand.MESSAGE_USAGE)); + } + final String lower = matcher.group("lower").strip(); + final String upper = matcher.group("upper").strip(); + Price lowerBound = new Price(Double.parseDouble(lower)); + Price upperBound = new Price(Double.parseDouble(upper)); + + if ((lowerBound.getPrice() > upperBound.getPrice() && (!upperBound.isNotApplicablePrice())) + || ((!lowerBound.isNotApplicablePrice()) && lowerBound.getPrice() < 0) + || ((!upperBound.isNotApplicablePrice()) && upperBound.getPrice() < 0) + ) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterOrderCommand.MESSAGE_USAGE)); + } + return new PriceRangePredicate<>(lowerBound, upperBound); + default: + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterOrderCommand.MESSAGE_USAGE)); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/SortCommandParser.java b/src/main/java/seedu/address/logic/parser/SortCommandParser.java new file mode 100644 index 00000000000..915fde80719 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SortCommandParser.java @@ -0,0 +1,168 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Comparator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.sortcommands.SortBuyerCommand; +import seedu.address.logic.commands.sortcommands.SortCommand; +import seedu.address.logic.commands.sortcommands.SortDelivererCommand; +import seedu.address.logic.commands.sortcommands.SortOrderCommand; +import seedu.address.logic.commands.sortcommands.SortPetCommand; +import seedu.address.logic.commands.sortcommands.SortSupplierCommand; +import seedu.address.logic.commands.util.CommandUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.util.SortCommandParserUtil; +import seedu.address.model.order.Order; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; + +/** + * Parses input arguments and creates a subclass of the {@code SortCommand}. + */ +public class SortCommandParser implements Parser { + + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?.\\S*)(?.*)"); + private final Integer firstAttributePos = 0; + + @Override + public SortCommand parse(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + + final String listType = matcher.group("listType").trim(); + final String attributes = matcher.group("attributes").trim(); + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_BUYER_PARAMETER, listType)) { + return parseToSortBuyerCommand(attributes); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SUPPLIER_PARAMETER, listType)) { + return parseToSortSupplierCommand(attributes); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_DELIVERER_PARAMETER, listType)) { + return parseToSortDelivererCommand(attributes); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_ORDER_PARAMETER, listType)) { + return parseToSortOrderCommand(attributes); + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_PET_PARAMETER, listType)) { + return parseToSortPetCommand(attributes); + } + + throw new ParseException(String.format(SortCommand.MESSAGE_UNKNOWN_LIST, listType, + SortCommand.MESSAGE_SUPPORTED_LIST)); + } + + private SortCommand parseToSortPetCommand(String attributes) throws ParseException { + requireNonNull(attributes); + return new SortPetCommand(getPetComparator(attributes)); + } + + private SortCommand parseToSortOrderCommand(String attributes) throws ParseException { + requireNonNull(attributes); + return new SortOrderCommand(getOrderComparator(attributes)); + } + + private SortCommand parseToSortDelivererCommand(String attributes) throws ParseException { + requireNonNull(attributes); + return new SortDelivererCommand(getDelivererComparator(attributes)); + } + + private SortCommand parseToSortSupplierCommand(String attributes) throws ParseException { + requireNonNull(attributes); + return new SortSupplierCommand(getSupplierComparator(attributes)); + } + + private SortCommand parseToSortBuyerCommand(String attributes) throws ParseException { + requireNonNull(attributes); + Comparator comparator = getBuyerComparator(attributes); + return new SortBuyerCommand(comparator); + } + + private Comparator getBuyerComparator(String attributes) throws ParseException { + String[] attributesArr = attributes.split("\\s+"); + assertAlphabets(attributesArr[firstAttributePos]); + Comparator comparator = SortCommandParserUtil.parseToSelectedBuyerComparator( + attributesArr[firstAttributePos]); + for (int i = 1; i < attributesArr.length; i++) { + assertAlphabets(attributesArr[i]); + comparator = comparator.thenComparing( + SortCommandParserUtil.parseToSelectedBuyerComparator(attributesArr[i])); + } + return comparator; + } + + private Comparator getSupplierComparator(String attributes) throws ParseException { + String[] attributesArr = attributes.split("\\s+"); + assertAlphabets(attributesArr[firstAttributePos]); + Comparator comparator = SortCommandParserUtil.parseToSelectedSupplierComparator( + attributesArr[firstAttributePos]); + for (int i = 1; i < attributesArr.length; i++) { + assertAlphabets(attributesArr[i]); + comparator = comparator.thenComparing( + SortCommandParserUtil.parseToSelectedSupplierComparator(attributesArr[i])); + } + return comparator; + } + + private Comparator getDelivererComparator(String attributes) throws ParseException { + String[] attributesArr = attributes.split("\\s+"); + assertAlphabets(attributesArr[firstAttributePos]); + Comparator comparator = SortCommandParserUtil.parseToSelectedDelivererComparator( + attributesArr[firstAttributePos]); + for (int i = 1; i < attributesArr.length; i++) { + assertAlphabets(attributesArr[i]); + comparator = comparator.thenComparing( + SortCommandParserUtil.parseToSelectedDelivererComparator(attributesArr[i])); + } + return comparator; + } + + private Comparator getOrderComparator(String attributes) throws ParseException { + String[] attributesArr = attributes.split("\\s+"); + assertAlphabets(attributesArr[firstAttributePos]); + Comparator comparator = SortCommandParserUtil.parseToSelectedOrderComparator( + attributesArr[firstAttributePos]); + for (int i = 1; i < attributesArr.length; i++) { + assertAlphabets(attributesArr[i]); + comparator = comparator.thenComparing( + SortCommandParserUtil.parseToSelectedOrderComparator(attributesArr[i])); + } + return comparator; + } + + private Comparator getPetComparator(String attributes) throws ParseException { + String[] attributesArr = attributes.split("\\s+"); + assertAlphabets(attributesArr[firstAttributePos]); + Comparator comparator = SortCommandParserUtil.parseToSelectedPetComparator( + attributesArr[firstAttributePos]); + for (int i = 1; i < attributesArr.length; i++) { + assertAlphabets(attributesArr[i]); + comparator = comparator.thenComparing( + SortCommandParserUtil.parseToSelectedPetComparator(attributesArr[i])); + } + return comparator; + } + + private boolean isAlphabets(String attribute) { + return attribute != null && attribute.matches("^[a-zA-Z]*$"); + } + + private void assertAlphabets(String attribute) throws ParseException { + if (!isAlphabets(attribute)) { + throw new ParseException(String.format(SortCommand.MESSAGE_ONLY_ALPHABET_PARAMETERS, attribute)); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/addcommandparser/AddBuyerCommandParser.java b/src/main/java/seedu/address/logic/parser/addcommandparser/AddBuyerCommandParser.java new file mode 100644 index 00000000000..b9bef902a9d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/addcommandparser/AddBuyerCommandParser.java @@ -0,0 +1,28 @@ +package seedu.address.logic.parser.addcommandparser; + +import seedu.address.logic.commands.addcommands.AddBuyerCommand; +import seedu.address.logic.commands.addcommands.AddPersonCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.PersonCategory; + +/** + * Parses input arguments and creates an {@code AddBuyerCommand}. + */ +public class AddBuyerCommandParser implements Parser { + + public AddBuyerCommandParser() { + } + + /** + * Parses the given {@code String} of arguments in the context of the AddBuyerCommand + * and returns an AddBuyerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddBuyerCommand parse(String args) throws ParseException { + AddPersonCommand addPersonCommand = ParserUtil.parseAddPersonCommand(args, PersonCategory.BUYER); + assert(addPersonCommand instanceof AddBuyerCommand); + return (AddBuyerCommand) addPersonCommand; + } +} diff --git a/src/main/java/seedu/address/logic/parser/addcommandparser/AddCommandWithPopupParser.java b/src/main/java/seedu/address/logic/parser/addcommandparser/AddCommandWithPopupParser.java new file mode 100644 index 00000000000..d2f566971cb --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/addcommandparser/AddCommandWithPopupParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser.addcommandparser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.addcommands.AddCommandWithPopup; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses the input arguments and creates an {@code AddCommandWithPopup}. + */ +public class AddCommandWithPopupParser implements Parser { + + @Override + public AddCommandWithPopup parse(String userInput) throws ParseException { + requireNonNull(userInput); + userInput = userInput.trim().toUpperCase(); + switch (userInput) { + case AddCommandWithPopup.ADD_BUYER: + return new AddCommandWithPopup(AddCommandWithPopup.ADD_BUYER); + case AddCommandWithPopup.ADD_SUPPLIER: + return new AddCommandWithPopup(AddCommandWithPopup.ADD_SUPPLIER); + default: + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommandWithPopup.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/addcommandparser/AddDelivererCommandParser.java b/src/main/java/seedu/address/logic/parser/addcommandparser/AddDelivererCommandParser.java new file mode 100644 index 00000000000..c5d30624833 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/addcommandparser/AddDelivererCommandParser.java @@ -0,0 +1,28 @@ +package seedu.address.logic.parser.addcommandparser; + +import seedu.address.logic.commands.addcommands.AddDelivererCommand; +import seedu.address.logic.commands.addcommands.AddPersonCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.PersonCategory; + +/** + * Parses input arguments and creates an {@code AddDelivererCommand}. + */ +public class AddDelivererCommandParser implements Parser { + + public AddDelivererCommandParser() { + } + + /** + * Parses the given {@code String} of arguments in the context of the AddDelivererCommand + * and returns an AddDelivererCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddDelivererCommand parse(String args) throws ParseException { + AddPersonCommand addPersonCommand = ParserUtil.parseAddPersonCommand(args, PersonCategory.DELIVERER); + assert(addPersonCommand instanceof AddDelivererCommand); + return (AddDelivererCommand) addPersonCommand; + } +} diff --git a/src/main/java/seedu/address/logic/parser/addcommandparser/AddOrderCommandParser.java b/src/main/java/seedu/address/logic/parser/addcommandparser/AddOrderCommandParser.java new file mode 100644 index 00000000000..db86ff84aa6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/addcommandparser/AddOrderCommandParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser.addcommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MISSING_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_ADDITIONAL_REQUESTS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_PRICE_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_REQUESTS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_STATUS; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.addcommands.AddOrderCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.Order; + +/** + * Parses the argument to create an {@code AddOrderCommand}. + */ +public class AddOrderCommandParser implements Parser { + + /** + * Parses {@code userInput} into a command and returns it. + * + * @param userInput The string argument. + * @throws ParseException if {@code userInput} does not conform the expected format + */ + @Override + public AddOrderCommand parse(String userInput) throws ParseException { + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(userInput, + PREFIX_ORDER_STATUS, + PREFIX_ORDER_REQUESTS, + PREFIX_ORDER_PRICE, + PREFIX_ORDER_PRICE_RANGE, + PREFIX_ORDER_ADDITIONAL_REQUESTS, + PREFIX_ORDER_DATE); + + String preamble = argMultimap.getPreamble(); + if (preamble.isBlank()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_MISSING_INDEX + + AddOrderCommand.MESSAGE_USAGE_EXISTING_BUYER)); + } + + String indexStr = preamble.split(" ")[0]; + Index index = ParserUtil.parseIndex(indexStr); + Order order = ParserUtil.parseOrder(userInput, true); + + return new AddOrderCommand(order, index); + } +} diff --git a/src/main/java/seedu/address/logic/parser/addcommandparser/AddPetCommandParser.java b/src/main/java/seedu/address/logic/parser/addcommandparser/AddPetCommandParser.java new file mode 100644 index 00000000000..4327f030815 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/addcommandparser/AddPetCommandParser.java @@ -0,0 +1,64 @@ +package seedu.address.logic.parser.addcommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_MISSING_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_CERTIFICATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_COLOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_COLOR_PATTERN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_DATE_OF_BIRTH; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_HEIGHT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_SPECIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_VACCINATION_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_WEIGHT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.addcommands.AddPetCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.pet.Pet; + +/** + * Parses input arguments and creates an {@code AddPetCommand}. + */ +public class AddPetCommandParser implements Parser { + + public AddPetCommandParser() { + } + + /** + * Parses the given {@code String} of arguments in the context of the AddPetCommand + * and returns an AddPetCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddPetCommand parse(String args) throws ParseException { + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_PET_NAME, + PREFIX_PET_DATE_OF_BIRTH, + PREFIX_PET_COLOR, + PREFIX_PET_COLOR_PATTERN, + PREFIX_PET_HEIGHT, + PREFIX_PET_CERTIFICATE, + PREFIX_PET_SPECIES, + PREFIX_PET_VACCINATION_STATUS, + PREFIX_PET_PRICE, + PREFIX_PET_WEIGHT); + + String preamble = argMultimap.getPreamble(); + if (preamble.isBlank()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_MISSING_INDEX + + AddPetCommand.MESSAGE_USAGE_EXISTING_SUPPLIER)); + } + + String indexStr = preamble.split(" ")[0]; + Index index = ParserUtil.parseIndex(indexStr); + Pet pet = ParserUtil.parsePet(args, true); + + return new AddPetCommand(pet, index); + } +} diff --git a/src/main/java/seedu/address/logic/parser/addcommandparser/AddSupplierCommandParser.java b/src/main/java/seedu/address/logic/parser/addcommandparser/AddSupplierCommandParser.java new file mode 100644 index 00000000000..281b2739a9b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/addcommandparser/AddSupplierCommandParser.java @@ -0,0 +1,28 @@ +package seedu.address.logic.parser.addcommandparser; + +import seedu.address.logic.commands.addcommands.AddPersonCommand; +import seedu.address.logic.commands.addcommands.AddSupplierCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.PersonCategory; + +/** + * Parses input arguments and creates an {@code AddSupplierCommand}. + */ +public class AddSupplierCommandParser implements Parser { + + public AddSupplierCommandParser() { + } + + /** + * Parses the given {@code String} of arguments in the context of the AddSupplierCommand + * and returns an AddSupplierCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddSupplierCommand parse(String args) throws ParseException { + AddPersonCommand addPersonCommand = ParserUtil.parseAddPersonCommand(args, PersonCategory.SUPPLIER); + assert(addPersonCommand instanceof AddSupplierCommand); + return (AddSupplierCommand) addPersonCommand; + } +} diff --git a/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteBuyerCommandParser.java b/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteBuyerCommandParser.java new file mode 100644 index 00000000000..80292e94730 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteBuyerCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser.deletecommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.deletecommands.DeleteBuyerCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a {@code DeleteBuyerCommand}. + */ +public class DeleteBuyerCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteBuyerCommand + * and returns a DeleteBuyerCommand object for execution. + * @throws ParseException If the user input does not conform the expected format. + */ + public DeleteBuyerCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteBuyerCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteBuyerCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteDelivererCommandParser.java b/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteDelivererCommandParser.java new file mode 100644 index 00000000000..33d1c00ecda --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteDelivererCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser.deletecommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.deletecommands.DeleteDelivererCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a {@code DeleteDelivererCommand}. + */ +public class DeleteDelivererCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteDelivererCommand + * and returns a DeleteDelivererCommand object for execution. + * @throws ParseException If the user input does not conform the expected format. + */ + public DeleteDelivererCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteDelivererCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteDelivererCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteOrderCommandParser.java b/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteOrderCommandParser.java new file mode 100644 index 00000000000..cb8dec788e8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteOrderCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser.deletecommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.deletecommands.DeleteOrderCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a {@code DeleteOrderCommand}. + */ +public class DeleteOrderCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteOrderCommand + * and returns a DeleteOrderCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteOrderCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteOrderCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteOrderCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/deletecommandparser/DeletePetCommandParser.java b/src/main/java/seedu/address/logic/parser/deletecommandparser/DeletePetCommandParser.java new file mode 100644 index 00000000000..fa092512dd6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deletecommandparser/DeletePetCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser.deletecommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.deletecommands.DeletePetCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a {@code DeletePetCommand}. + */ +public class DeletePetCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeletePetCommand + * and returns a DeletePetCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeletePetCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeletePetCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeletePetCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteSupplierCommandParser.java b/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteSupplierCommandParser.java new file mode 100644 index 00000000000..82d857d1b1d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/deletecommandparser/DeleteSupplierCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser.deletecommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.deletecommands.DeleteSupplierCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a {@code DeleteSupplierCommand}. + */ +public class DeleteSupplierCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteSupplierCommand + * and returns a DeleteSupplierCommand object for execution. + * @throws ParseException If the user input does not conform the expected format. + */ + public DeleteSupplierCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteSupplierCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteSupplierCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/editcommandparser/EditBuyerCommandParser.java similarity index 52% rename from src/main/java/seedu/address/logic/parser/EditCommandParser.java rename to src/main/java/seedu/address/logic/parser/editcommandparser/EditBuyerCommandParser.java index 845644b7dea..af796c900ad 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/editcommandparser/EditBuyerCommandParser.java @@ -1,48 +1,49 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.editcommandparser; import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.commands.editcommands.EditBuyerCommand; +import seedu.address.logic.commands.editcommands.EditCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; /** - * Parses input arguments and creates a new EditCommand object + * Parses input arguments and creates a new EditBuyerCommand object. */ -public class EditCommandParser implements Parser { +public class EditBuyerCommandParser 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 + * Parses the given {@code String} of arguments in the context of the EditBuyerCommand + * and returns a EditBuyerCommand object for execution. + * + * @throws ParseException If the user input does not conform the expected format. */ - public EditCommand parse(String args) throws ParseException { + @Override + public EditBuyerCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_LOCATION); 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, EditBuyerCommand.MESSAGE_USAGE), pe); } - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + EditCommand.EditPersonDescriptor editPersonDescriptor = new EditCommand.EditPersonDescriptor(); if (argMultimap.getValue(PREFIX_NAME).isPresent()) { editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); } @@ -55,28 +56,15 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + if (argMultimap.getValue(PREFIX_LOCATION).isPresent()) { + editPersonDescriptor.setLocation(ParserUtil.parseLocation(argMultimap.getValue(PREFIX_LOCATION).get())); + } if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); + return new EditBuyerCommand(index, editPersonDescriptor); } } diff --git a/src/main/java/seedu/address/logic/parser/editcommandparser/EditDelivererCommandParser.java b/src/main/java/seedu/address/logic/parser/editcommandparser/EditDelivererCommandParser.java new file mode 100644 index 00000000000..3197fd77d39 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/editcommandparser/EditDelivererCommandParser.java @@ -0,0 +1,70 @@ +package seedu.address.logic.parser.editcommandparser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.editcommands.EditCommand; +import seedu.address.logic.commands.editcommands.EditDelivererCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditDelivererCommand object. + */ +public class EditDelivererCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditDelivererCommand + * and returns a EditDelivererCommand object for execution. + * @throws ParseException If the user input does not conform the expected format. + */ + @Override + public EditDelivererCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_LOCATION); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, EditDelivererCommand.MESSAGE_USAGE), pe); + } + + EditCommand.EditPersonDescriptor editPersonDescriptor = new EditCommand.EditPersonDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + } + if (argMultimap.getValue(PREFIX_LOCATION).isPresent()) { + editPersonDescriptor.setLocation(ParserUtil.parseLocation(argMultimap.getValue(PREFIX_LOCATION).get())); + } + + if (!editPersonDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } + + return new EditDelivererCommand(index, editPersonDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/editcommandparser/EditSupplierCommandParser.java b/src/main/java/seedu/address/logic/parser/editcommandparser/EditSupplierCommandParser.java new file mode 100644 index 00000000000..35e2f5aeb37 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/editcommandparser/EditSupplierCommandParser.java @@ -0,0 +1,70 @@ +package seedu.address.logic.parser.editcommandparser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.editcommands.EditCommand; +import seedu.address.logic.commands.editcommands.EditSupplierCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new EditSupplierCommand object. + */ +public class EditSupplierCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditSupplierCommand + * and returns a EditSupplierCommand object for execution. + * @throws ParseException If the user input does not conform the expected format. + */ + @Override + public EditSupplierCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_LOCATION); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, EditSupplierCommand.MESSAGE_USAGE), pe); + } + + EditCommand.EditPersonDescriptor editPersonDescriptor = new EditCommand.EditPersonDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + } + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + } + if (argMultimap.getValue(PREFIX_LOCATION).isPresent()) { + editPersonDescriptor.setLocation(ParserUtil.parseLocation(argMultimap.getValue(PREFIX_LOCATION).get())); + } + + if (!editPersonDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } + + return new EditSupplierCommand(index, editPersonDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/filtercommandparser/FilterOrderCommandParser.java b/src/main/java/seedu/address/logic/parser/filtercommandparser/FilterOrderCommandParser.java new file mode 100644 index 00000000000..156672c53e9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/filtercommandparser/FilterOrderCommandParser.java @@ -0,0 +1,79 @@ +package seedu.address.logic.parser.filtercommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_ADDITIONAL_REQUESTS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_PRICE_RANGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_STATUS; + +import java.util.function.Predicate; + +import seedu.address.logic.commands.filtercommands.FilterOrderCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.PredicateParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.Order; + +/** + * Parses input arguments and creates a {@code FilterOrderCommand}. + */ +public class FilterOrderCommandParser implements Parser { + + private static Predicate defaultPredicate = new Predicate() { + @Override + public boolean test(Order order) { + return true; + } + public boolean equals(Object object) { + return object instanceof Predicate; + } + }; + + /** + * Parses the given {@code String} of arguments in the context of the FilterOrderCommand + * and returns a FilterOrderCommand object for execution. + * @throws ParseException if the user input does not conform the expected format. + */ + public FilterOrderCommand parse(String trimmedArgs) throws ParseException { + + boolean isTokenized = false; + + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterOrderCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(trimmedArgs, PREFIX_ORDER_ADDITIONAL_REQUESTS, PREFIX_ORDER_STATUS, + PREFIX_ORDER_PRICE_RANGE); + + Predicate additionalRequestPredicate = defaultPredicate; + Predicate orderStatusPredicate = defaultPredicate; + Predicate priceRangePredicate = defaultPredicate; + + if (argMultimap.getValue(PREFIX_ORDER_ADDITIONAL_REQUESTS).isPresent()) { + additionalRequestPredicate = PredicateParser.parseOrder(argMultimap + .getValue(PREFIX_ORDER_ADDITIONAL_REQUESTS).get(), PREFIX_ORDER_ADDITIONAL_REQUESTS.getPrefix()); + isTokenized = true; + } + + if (argMultimap.getValue(PREFIX_ORDER_STATUS).isPresent()) { + orderStatusPredicate = PredicateParser.parseOrder(argMultimap.getValue(PREFIX_ORDER_STATUS).get(), + PREFIX_ORDER_STATUS.getPrefix()); + isTokenized = true; + } + + if (argMultimap.getValue(PREFIX_ORDER_PRICE_RANGE).isPresent()) { + priceRangePredicate = PredicateParser.parseOrder(argMultimap.getValue(PREFIX_ORDER_PRICE_RANGE).get(), + PREFIX_ORDER_PRICE_RANGE.getPrefix()); + isTokenized = true; + } + + if (!isTokenized) { + throw new ParseException(FilterOrderCommand.MESSAGE_NOT_FILTERED); + } + + return new FilterOrderCommand(additionalRequestPredicate, orderStatusPredicate, priceRangePredicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/filtercommandparser/FilterPetCommandParser.java b/src/main/java/seedu/address/logic/parser/filtercommandparser/FilterPetCommandParser.java new file mode 100644 index 00000000000..7a9c02c493c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/filtercommandparser/FilterPetCommandParser.java @@ -0,0 +1,102 @@ +package seedu.address.logic.parser.filtercommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_COLOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_PRICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_SPECIES; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PET_VACCINATION_STATUS; + +import java.util.function.Predicate; + +import seedu.address.logic.commands.filtercommands.FilterPetCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.PredicateParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.pet.Pet; + +/** + * Parses input arguments and creates a {@code FilterPetCommand}. + */ +public class FilterPetCommandParser implements Parser { + public static final String COLOR_PREFIX = "p_c"; + public static final String PET_NAME_PREFIX = "p_n"; + public static final String PRICE_PREFIX = "p_p"; + public static final String SPECIES_PREFIX = "p_s"; + public static final String VACCINATION_PREFIX = "p_v"; + + private static Predicate defaultPredicate = new Predicate() { + @Override + public boolean test(Pet pet) { + return true; + } + public boolean equals(Object object) { + return object instanceof Predicate; + } + }; + + /** + * Parses the given {@code String} of arguments in the context of the FilterPetCommand + * and returns a FilterPetCommand object for execution. + * @throws ParseException if the user input does not conform the expected format. + */ + public FilterPetCommand parse(String trimmedArgs) throws ParseException { + + boolean isTokenized = false; + + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterPetCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(trimmedArgs, PREFIX_PET_COLOR, PREFIX_PET_NAME, + PREFIX_PET_PRICE, PREFIX_PET_SPECIES, PREFIX_PET_VACCINATION_STATUS); + + Predicate colorPredicate = defaultPredicate; + Predicate namePredicate = defaultPredicate; + Predicate pricePredicate = defaultPredicate; + Predicate speciesPredicate = defaultPredicate; + Predicate vaccinationPredicate = defaultPredicate; + + if (argMultimap.getValue(PREFIX_PET_COLOR).isPresent()) { + colorPredicate = PredicateParser.parsePet(argMultimap.getValue(PREFIX_PET_COLOR).get(), + PREFIX_PET_COLOR.getPrefix()); + isTokenized = true; + } + + if (argMultimap.getValue(PREFIX_PET_NAME).isPresent()) { + colorPredicate = PredicateParser.parsePet(argMultimap.getValue(PREFIX_PET_NAME).get(), + PREFIX_PET_NAME.getPrefix()); + isTokenized = true; + } + + if (argMultimap.getValue(PREFIX_PET_PRICE).isPresent()) { + colorPredicate = PredicateParser.parsePet(argMultimap.getValue(PREFIX_PET_PRICE).get(), + PREFIX_PET_PRICE.getPrefix()); + isTokenized = true; + } + + if (argMultimap.getValue(PREFIX_PET_SPECIES).isPresent()) { + colorPredicate = PredicateParser.parsePet(argMultimap.getValue(PREFIX_PET_SPECIES).get(), + PREFIX_PET_SPECIES.getPrefix()); + isTokenized = true; + } + + if (argMultimap.getValue(PREFIX_PET_VACCINATION_STATUS).isPresent()) { + colorPredicate = PredicateParser.parsePet(argMultimap.getValue(PREFIX_PET_VACCINATION_STATUS).get(), + PREFIX_PET_VACCINATION_STATUS.getPrefix()); + isTokenized = true; + } + + if (!isTokenized) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterPetCommand.MESSAGE_USAGE)); + } + + return new FilterPetCommand(colorPredicate, namePredicate, pricePredicate, speciesPredicate, + vaccinationPredicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/findcommandparser/FindBuyerCommandParser.java b/src/main/java/seedu/address/logic/parser/findcommandparser/FindBuyerCommandParser.java new file mode 100644 index 00000000000..9aa3ae8b5fe --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/findcommandparser/FindBuyerCommandParser.java @@ -0,0 +1,40 @@ +package seedu.address.logic.parser.findcommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.function.Predicate; + +import seedu.address.logic.commands.findcommands.FindBuyerCommand; +import seedu.address.logic.commands.findcommands.FindCommand; +import seedu.address.logic.parser.PredicateParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Buyer; + +/** + * Parses input arguments and creates a {@code FindBuyerCommand}. + */ +public class FindBuyerCommandParser extends FindCommandParser { + public static final String PARSE_WORD = "find-b"; + + /** + * Parses the given {@code String} of arguments in the context of the FindBuyerCommand + * and returns a FindBuyerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format. + */ + public FindBuyerCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + if (moreThanOnePrefix(trimmedArgs)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, "More than 1 prefix present")); + } + + Predicate buyerPredicate = PredicateParser.parseBuyer(trimmedArgs); + + return new FindBuyerCommand(buyerPredicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/findcommandparser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/findcommandparser/FindCommandParser.java new file mode 100644 index 00000000000..149b0b55e6c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/findcommandparser/FindCommandParser.java @@ -0,0 +1,84 @@ +package seedu.address.logic.parser.findcommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LOCATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; + +import java.util.function.Predicate; + +import seedu.address.logic.commands.findcommands.FindCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.PredicateParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.PersonCategory; +import seedu.address.model.person.Supplier; + +/** + * Parses input arguments and creates a {@code FindCommand}. + */ +public class FindCommandParser 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 { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + if (moreThanOnePrefix(trimmedArgs)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, "More than 1 prefix present")); + } + Predicate buyerPredicate = PredicateParser.parseBuyer(trimmedArgs); + Predicate delivererPredicate = PredicateParser.parseDeliverer(trimmedArgs); + Predicate supplierPredicate = PredicateParser.parseSupplier(trimmedArgs); + + return new FindCommand(buyerPredicate, delivererPredicate, supplierPredicate, + PersonCategory.getFromString("Buyer")); + } + + /** + * Returns if a string input contains more than one prefix. + * + * @param input String input given by user. + * @return Whether the input has more than one prefix. + */ + public boolean moreThanOnePrefix(String input) { + int totalPrefixesPresent = countOccurrences(PREFIX_ADDRESS.getPrefix(), input) + + countOccurrences(PREFIX_EMAIL.getPrefix(), input) + + countOccurrences(PREFIX_INDEX.getPrefix(), input) + + countOccurrences(PREFIX_LOCATION.getPrefix(), input) + + countOccurrences(PREFIX_NAME.getPrefix(), input) + + countOccurrences(PREFIX_PHONE.getPrefix(), input); + return totalPrefixesPresent > 1; + } + + /** + * Counts the number of occurrences of prefix in string. + * + * @param prefix Prefix to count. + * @param input String input given by user. + * @return Number of occurrences of that prefix. + */ + public int countOccurrences(String prefix, String input) { + int limit = input.length() - prefix.length(); + int count = 0; + for (int i = 0; i < limit + 1; i++) { + if (input.substring(i, i + prefix.length()).equals(prefix)) { + count += 1; + } + } + return count; + } + +} diff --git a/src/main/java/seedu/address/logic/parser/findcommandparser/FindDelivererCommandParser.java b/src/main/java/seedu/address/logic/parser/findcommandparser/FindDelivererCommandParser.java new file mode 100644 index 00000000000..3cf4c17ba9e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/findcommandparser/FindDelivererCommandParser.java @@ -0,0 +1,40 @@ +package seedu.address.logic.parser.findcommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.function.Predicate; + +import seedu.address.logic.commands.findcommands.FindCommand; +import seedu.address.logic.commands.findcommands.FindDelivererCommand; +import seedu.address.logic.parser.PredicateParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Deliverer; + +/** + * Parses input arguments and creates a {@code FindDelivererCommand}. + */ +public class FindDelivererCommandParser extends FindCommandParser { + public static final String PARSE_WORD = "find-d"; + + /** + * Parses the given {@code String} of arguments in the context of the FindDelivererCommand + * and returns a FindDelivererCommand object for execution. + * @throws ParseException if the user input does not conform the expected format. + */ + public FindDelivererCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + if (moreThanOnePrefix(trimmedArgs)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, "More than 1 prefix present")); + } + + Predicate delivererPredicate = PredicateParser.parseDeliverer(trimmedArgs); + + return new FindDelivererCommand(delivererPredicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/findcommandparser/FindSupplierCommandParser.java b/src/main/java/seedu/address/logic/parser/findcommandparser/FindSupplierCommandParser.java new file mode 100644 index 00000000000..9744e380377 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/findcommandparser/FindSupplierCommandParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser.findcommandparser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.function.Predicate; + +import seedu.address.logic.commands.findcommands.FindCommand; +import seedu.address.logic.commands.findcommands.FindSupplierCommand; +import seedu.address.logic.parser.PredicateParser; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Supplier; + +/** + * Parses input arguments and creates a {@code FindSupplierCommand}. + */ +public class FindSupplierCommandParser extends FindCommandParser { + public static final String PARSE_WORD = "find-s"; + /** + * Parses the given {@code String} of arguments in the context of the FindSupplierCommand + * and returns a FindSupplierCommand object for execution. + * @throws ParseException if the user input does not conform the expected format. + */ + public FindSupplierCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + + if (moreThanOnePrefix(trimmedArgs)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, "More than 1 prefix present")); + } + + Predicate supplierPredicate = PredicateParser.parseSupplier(trimmedArgs); + + return new FindSupplierCommand(supplierPredicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/util/SortCommandParserUtil.java b/src/main/java/seedu/address/logic/parser/util/SortCommandParserUtil.java new file mode 100644 index 00000000000..abc061b42ea --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/util/SortCommandParserUtil.java @@ -0,0 +1,252 @@ +package seedu.address.logic.parser.util; + +import java.util.Comparator; + +import seedu.address.logic.commands.sortcommands.SortBuyerCommand; +import seedu.address.logic.commands.sortcommands.SortDelivererCommand; +import seedu.address.logic.commands.sortcommands.SortOrderCommand; +import seedu.address.logic.commands.sortcommands.SortPetCommand; +import seedu.address.logic.commands.sortcommands.SortSupplierCommand; +import seedu.address.logic.commands.util.CommandUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.Order; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Person; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; + +/** + * Provides utils for SortCommand related classes. + */ +public class SortCommandParserUtil { + + //Comparators for person + private static final Comparator PERSON_ADDRESS_COMPARATOR = (o1, o2) -> + o1.getAddress().compareTo(o2.getAddress()); + private static final Comparator PERSON_EMAIL_COMPARATOR = (o1, o2) -> + o1.getEmail().compareTo(o2.getEmail()); + private static final Comparator PERSON_LOCATION_COMPARATOR = (o1, o2) -> + o1.getLocation().compareTo(o2.getLocation()); + private static final Comparator PERSON_NAME_COMPARATOR = (o1, o2) -> + o1.getName().compareTo(o2.getName()); + private static final Comparator PERSON_PHONE_COMPARATOR = (o1, o2) -> + o1.getPhone().compareTo(o2.getPhone()); + + //Comparators for Buyer + private static final Comparator BUYER_COMPARATOR = Buyer::compareTo; + + //Comparators for Supplier + private static final Comparator SUPPLIER_COMPARATOR = Supplier::compareTo; + + //Comparators for Deliverer + private static final Comparator DELIVERER_COMPARATOR = Deliverer::compareTo; + + //Comparators for Order + private static final Comparator ORDER_PRICE_RANGE_COMPARATOR = Comparator.comparing( + Order::getRequestedPriceRange); + private static final Comparator ORDER_DUE_DATE_COMPARATOR = Comparator.comparing(Order::getByDate); + private static final Comparator ORDER_PRICE_COMPARATOR = Comparator.comparing(Order::getSettledPrice); + private static final Comparator ORDER_STATUS_COMPARATOR = Comparator.comparing(Order::getOrderStatus); + + //Comparators for Pet + private static final Comparator PET_NAME_COMPARATOR = (o1, o2) -> + o1.getName().compareTo(o2.getName()); + private static final Comparator PET_COLOR_COMPARATOR = Comparator.comparing(Pet::getColor); + private static final Comparator PET_COLOR_PATTERN_COMPARATOR = Comparator.comparing(Pet::getColorPattern); + private static final Comparator PET_BIRTH_DATE_COMPARATOR = Comparator.comparing(Pet::getDateOfBirth); + private static final Comparator PET_SPECIES_COMPARATOR = Comparator.comparing(Pet::getSpecies); + private static final Comparator PET_HEIGHT_COMPARATOR = Comparator.comparing(Pet::getHeight); + private static final Comparator PET_WEIGHT_COMPARATOR = Comparator.comparing(Pet::getWeight); + private static final Comparator PET_PRICE_COMPARATOR = Comparator.comparing(Pet::getPrice); + + private static Comparator parseToSelectedPersonComparator(String attribute) { + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_ADDRESS_PARAMETER, attribute)) { + return PERSON_ADDRESS_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_EMAIL_PARAMETER, attribute)) { + return PERSON_EMAIL_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_LOCATION_PARAMETER, attribute)) { + return PERSON_LOCATION_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_NAME_PARAMETER, attribute)) { + return PERSON_NAME_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_PHONE_PARAMETER, attribute)) { + return PERSON_PHONE_COMPARATOR; + } + + return null; + } + + /** + * Parses the input string to a buyer comparator according to the attribute specify by the input string. + * @param attribute The input string. + * @return The comparator. + * @throws ParseException Throws an exception when the input string species an attribute that is not supported + * for sorting. + */ + public static Comparator parseToSelectedBuyerComparator(String attribute) throws ParseException { + if (attribute.isEmpty() || CommandUtil.isValidParameter( + CommandUtil.ACCEPTABLE_SORT_ORDER_SIZE_PARAMETER, attribute)) { + return BUYER_COMPARATOR; + } + if (parseToSelectedPersonComparator(attribute) == null) { + throw new ParseException(String.format(SortBuyerCommand.MESSAGE_WRONG_ATTRIBUTE, attribute, + SortBuyerCommand.MESSAGE_USAGE)); + } + + /* + parseToSelectedPersonComparator() will always return a valid person comparator if not null. + */ + @SuppressWarnings("unchecked") + Comparator comparator = (Comparator) parseToSelectedPersonComparator(attribute); + + return comparator; + } + + /** + * Parses the input string to a supplier comparator according to the attribute specify by the input string. + * @param attribute The input string. + * @return The comparator. + * @throws ParseException Throws an exception when the input string species an attribute that is not supported + * for sorting. + */ + public static Comparator parseToSelectedSupplierComparator(String attribute) throws ParseException { + if (attribute.isEmpty() || CommandUtil.isValidParameter( + CommandUtil.ACCEPTABLE_SORT_PET_LIST_SIZE_PARAMETER, attribute)) { + return SUPPLIER_COMPARATOR; + } + if (parseToSelectedPersonComparator(attribute) == null) { + throw new ParseException(String.format(SortSupplierCommand.MESSAGE_WRONG_ATTRIBUTE, attribute, + SortSupplierCommand.MESSAGE_USAGE)); + } + + /* + parseToSelectedPersonComparator() will always return a valid person comparator if not null. + */ + @SuppressWarnings("unchecked") + Comparator comparator = (Comparator) parseToSelectedPersonComparator(attribute); + + return comparator; + } + + /** + * Parses the input string to a deliverer comparator according to the attribute specify by the input string. + * @param attribute The input string. + * @return The comparator. + * @throws ParseException Throws an exception when the input string species an attribute that is not supported + * for sorting. + */ + public static Comparator parseToSelectedDelivererComparator(String attribute) throws ParseException { + if (attribute.isEmpty() || CommandUtil.isValidParameter( + CommandUtil.ACCEPTABLE_SORT_ORDER_SIZE_PARAMETER, attribute)) { + + @SuppressWarnings("unchecked") + Comparator personNameComparator = (Comparator) PERSON_NAME_COMPARATOR; + + return personNameComparator; + } + if (parseToSelectedPersonComparator(attribute) == null) { + throw new ParseException(String.format(SortDelivererCommand.MESSAGE_WRONG_ATTRIBUTE, attribute, + SortSupplierCommand.MESSAGE_USAGE)); + } + + /* + parseToSelectedPersonComparator() will always return a valid person comparator if not null. + */ + @SuppressWarnings("unchecked") + Comparator comparator = (Comparator) parseToSelectedPersonComparator(attribute); + + return comparator; + } + + /** + * Parses the input string to an order comparator according to the attribute specify by the input string. + * @param attribute The input string. + * @return The comparator. + * @throws ParseException Throws an exception when the input string species an attribute that is not supported + * for sorting. + */ + public static Comparator parseToSelectedOrderComparator(String attribute) throws ParseException { + if (attribute.isEmpty() || CommandUtil.isValidParameter( + CommandUtil.ACCEPTABLE_SORT_DUE_DATE_PARAMETER, attribute)) { + return ORDER_DUE_DATE_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_PRICE_RANGE_PARAMETER, attribute)) { + return ORDER_PRICE_RANGE_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_PRICE_PARAMETER, attribute)) { + return ORDER_PRICE_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_STATUS_PARAMETER, attribute)) { + return ORDER_STATUS_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_DUE_DATE_PARAMETER, attribute)) { + return ORDER_DUE_DATE_COMPARATOR; + } + throw new ParseException(String.format(SortOrderCommand.MESSAGE_WRONG_ATTRIBUTE, attribute, + SortOrderCommand.MESSAGE_USAGE)); + + } + + + /** + * Parses the input string to a pet comparator according to the attribute specify by the input string. + * @param attribute The input string. + * @return The comparator. + * @throws ParseException Throws an exception when the input string species an attribute that is not supported + * for sorting. + */ + public static Comparator parseToSelectedPetComparator(String attribute) throws ParseException { + if (attribute.isEmpty() || CommandUtil.isValidParameter( + CommandUtil.ACCEPTABLE_SORT_PRICE_PARAMETER, attribute)) { + return PET_PRICE_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_NAME_PARAMETER, attribute)) { + return PET_NAME_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_COLOR_PARAMETER, attribute)) { + return PET_COLOR_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_COLOR_PATTERN_PARAMETER, attribute)) { + return PET_COLOR_PATTERN_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_BIRTH_DATE_PARAMETER, attribute)) { + return PET_BIRTH_DATE_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_SPECIES_PARAMETER, attribute)) { + return PET_SPECIES_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_HEIGHT_PARAMETER, attribute)) { + return PET_HEIGHT_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_WEIGHT_PARAMETER, attribute)) { + return PET_WEIGHT_COMPARATOR; + } + + if (CommandUtil.isValidParameter(CommandUtil.ACCEPTABLE_SORT_PRICE_PARAMETER, attribute)) { + return PET_PRICE_COMPARATOR; + } + + throw new ParseException(String.format(SortPetCommand.MESSAGE_WRONG_ATTRIBUTE, attribute, + SortPetCommand.MESSAGE_USAGE)); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..8b62a98306c 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -2,11 +2,21 @@ import static java.util.Objects.requireNonNull; +import java.util.Comparator; import java.util.List; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; +import seedu.address.commons.core.index.UniqueId; +import seedu.address.model.order.Order; +import seedu.address.model.order.UniqueOrderList; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Supplier; +import seedu.address.model.person.UniqueBuyerList; +import seedu.address.model.person.UniqueDelivererList; +import seedu.address.model.person.UniqueSupplierList; +import seedu.address.model.pet.Pet; +import seedu.address.model.pet.UniquePetList; /** * Wraps all data at the address-book level @@ -14,17 +24,25 @@ */ public class AddressBook implements ReadOnlyAddressBook { - private final UniquePersonList persons; + private final UniqueBuyerList buyers; + private final UniqueSupplierList suppliers; + private final UniqueDelivererList deliverers; + private final UniquePetList pets; + private final UniqueOrderList orders; /* * 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. + * among constructors. */ { - persons = new UniquePersonList(); + buyers = new UniqueBuyerList(); + suppliers = new UniqueSupplierList(); + deliverers = new UniqueDelivererList(); + pets = new UniquePetList(); + orders = new UniqueOrderList(); } public AddressBook() {} @@ -37,14 +55,30 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { resetData(toBeCopied); } - //// list overwrite operations + // list overwrite operations /** * Replaces the contents of the person list with {@code persons}. * {@code persons} must not contain duplicate persons. */ - public void setPersons(List persons) { - this.persons.setPersons(persons); + public void setBuyers(List persons) { + this.buyers.setPersons(persons); + } + + public void setSuppliers(List persons) { + this.suppliers.setPersons(persons); + } + + public void setDeliverers(List persons) { + this.deliverers.setPersons(persons); + } + + public void setPets(List pets) { + this.pets.setPets(pets); + } + + public void setOrders(List orders) { + this.orders.setOrders(orders); } /** @@ -53,25 +87,78 @@ public void setPersons(List persons) { public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); - setPersons(newData.getPersonList()); + setBuyers(newData.getBuyerList()); + setSuppliers(newData.getSupplierList()); + setDeliverers(newData.getDelivererList()); + setPets(newData.getPetList()); + setOrders(newData.getOrderList()); + } - //// person-level operations + // person-level operations + + /** + * Returns true if a person with the same identity as {@code Buyer} exists in the address book. + */ + public boolean hasBuyer(Buyer buyer) { + requireNonNull(buyer); + return buyers.contains(buyer); + } + + /** + * Returns true if a person with the same identity as {@code Supplier} exists in the address book. + */ + public boolean hasSupplier(Supplier supplier) { + requireNonNull(supplier); + return suppliers.contains(supplier); + } + + /** + * Returns true if a person with the same identity as {@code Deliverer} exists in the address book. + */ + public boolean hasDeliverer(Deliverer deliverer) { + requireNonNull(deliverer); + return deliverers.contains(deliverer); + } + + /** + * Returns true if a person with the same identity as {@code Pet} exists in the address book. + */ + public boolean hasPet(Pet pet) { + requireNonNull(pet); + return pets.contains(pet); + } /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a person with the same identity as {@code Order} exists in the address book. */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); + public boolean hasOrder(Order order) { + requireNonNull(order); + return orders.contains(order); } /** * Adds a person to the address book. * The person must not already exist in the address book. */ - public void addPerson(Person p) { - persons.add(p); + public void addBuyer(Buyer p) { + buyers.add(p); + } + + public void addSupplier(Supplier p) { + suppliers.add(p); + } + + public void addDeliverer(Deliverer p) { + deliverers.add(p); + } + + public void addOrder(Order p) { + orders.add(p); + } + + public void addPet(Pet p) { + pets.add(p); } /** @@ -79,42 +166,157 @@ public void addPerson(Person p) { * {@code target} must exist in the address book. * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); + public void setBuyer(Buyer target, Buyer editedBuyer) { + requireNonNull(editedBuyer); - persons.setPerson(target, editedPerson); + buyers.setPerson(target, editedBuyer); + } + + public void setSupplier(Supplier target, Supplier editedSupplier) { + requireNonNull(editedSupplier); + + suppliers.setPerson(target, editedSupplier); + } + + public void setDeliverer(Deliverer target, Deliverer editedDeliverer) { + requireNonNull(editedDeliverer); + + deliverers.setPerson(target, editedDeliverer); + } + + public void setPet(Pet target, Pet editedPet) { + requireNonNull(editedPet); + + pets.setPet(target, editedPet); + } + + public void setOrder(Order target, Order editedOrder) { + requireNonNull(editedOrder); + + orders.setOrder(target, editedOrder); } /** * 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 removeBuyer(Buyer key) { + buyers.remove(key); } - //// util methods + public void removeSupplier(Supplier key) { + suppliers.remove(key); + } + + public void removeDeliverer(Deliverer key) { + deliverers.remove(key); + } + + public void removePet(Pet key) { + pets.remove(key); + } + + public void removeOrder(Order key) { + orders.remove(key); + } + + public List getOrderFromId(List ids) { + return orders.getOrdersFromId(ids); + } + + public List getPetFromId(List ids) { + return pets.getPetsFromId(ids); + } @Override public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later + StringBuilder sb = new StringBuilder(); + sb.append(buyers.asUnmodifiableObservableList().size() + " buyers"); + sb.append("\n"); + sb.append(suppliers.asUnmodifiableObservableList().size() + " suppliers"); + sb.append("\n"); + sb.append(deliverers.asUnmodifiableObservableList().size() + " deliverers"); + sb.append("\n"); + sb.append(pets.asUnmodifiableObservableList().size() + " pets"); + sb.append("\n"); + sb.append(orders.asUnmodifiableObservableList().size() + " orders"); + sb.append("\n"); + return sb.toString(); + } + + /** + * Sorts the buyer list using the specified comparator. + * @param comparator The specified comparator. + */ + public void sortBuyer(Comparator comparator) { + buyers.sort(comparator); + } + + /** + * Sorts the supplier list using the specified comparator. + * @param comparator The specified comparator. + */ + public void sortSupplier(Comparator comparator) { + suppliers.sort(comparator); + } + + /** + * Sorts the deliverer list using the specified comparator. + * @param comparator The specified comparator. + */ + public void sortDeliverer(Comparator comparator) { + deliverers.sort(comparator); + } + + /** + * Sorts the order list using the specified comparator. + * @param comparator The specified comparator. + */ + public void sortOrder(Comparator comparator) { + orders.sort(comparator); + } + + /** + * Sorts the pet list using the specified comparator. + * @param comparator The specified comparator. + */ + public void sortPet(Comparator comparator) { + pets.sort(comparator); } @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); + public ObservableList getBuyerList() { + return buyers.asUnmodifiableObservableList(); } @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); + public ObservableList getSupplierList() { + return suppliers.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getDelivererList() { + return deliverers.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getPetList() { + return pets.asUnmodifiableObservableList(); } @Override - public int hashCode() { - return persons.hashCode(); + public ObservableList getOrderList() { + return orders.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddressBook // instanceof handles nulls + && buyers.equals(((AddressBook) other).buyers) + && suppliers.equals(((AddressBook) other).suppliers) + && deliverers.equals(((AddressBook) other).deliverers) + && pets.equals(((AddressBook) other).pets) + && orders.equals(((AddressBook) other).orders)); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..65bbf3d31dd 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,18 +1,29 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; +import seedu.address.model.order.Order; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; /** * 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_BUYERS = unused -> true; + Predicate PREDICATE_SHOW_ALL_SUPPLIERS = unused -> true; + Predicate PREDICATE_SHOW_ALL_DELIVERERS = unused -> true; + Predicate PREDICATE_SHOW_ALL_PETS = unused -> true; + Predicate PREDICATE_SHOW_ALL_ORDERS = unused -> true; + /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -55,33 +66,171 @@ public interface Model { /** * Returns true if a person with the same identity as {@code person} exists in the address book. */ - boolean hasPerson(Person person); + boolean hasBuyer(Buyer buyer); + boolean hasSupplier(Supplier supplier); + boolean hasDeliverer(Deliverer deliverer); + boolean hasPet(Pet pet); + boolean hasOrder(Order order); /** * Deletes the given person. * The person must exist in the address book. */ - void deletePerson(Person target); + void deleteBuyer(Buyer target); + void deleteSupplier(Supplier target); + void deleteDeliverer(Deliverer target); + void deletePet(Pet target); + void deleteOrder(Order target); /** * Adds the given person. * {@code person} must not already exist in the address book. */ - void addPerson(Person person); + void addBuyer(Buyer buyer); + void addSupplier(Supplier supplier); + void addDeliverer(Deliverer deliverer); + void addPet(Pet pet); + void addOrder(Order order); /** * Replaces the given person {@code target} with {@code editedPerson}. * {@code target} must exist in the address book. * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. */ - void setPerson(Person target, Person editedPerson); + void setBuyer(Buyer target, Buyer editedBuyer); + void setSupplier(Supplier target, Supplier editedSupplier); + void setDeliverer(Deliverer target, Deliverer editedDeliverer); + void setPet(Pet target, Pet editedPet); + void setOrder(Order target, Order editedOrder); + + /** + * Sorts the buyer list using the specified comparator. + * @param comparator The specified comparator. + */ + void sortBuyer(Comparator comparator); + + /** + * Sorts the supplier list using the specified comparator. + * @param comparator The specified comparator. + */ + void sortSupplier(Comparator comparator); + + /** + * Sorts the deliverer list using the specified comparator. + * @param comparator The specified comparator. + */ + void sortDeliverer(Comparator comparator); + + /** + * Sorts the order list using the specified comparator. + * @param comparator The specified comparator. + */ + void sortOrder(Comparator comparator); + + /** + * Sorts the pet list using the specified comparator. + * @param comparator The specified comparator. + */ + void sortPet(Comparator comparator); /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + ObservableList getFilteredBuyerList(); + ObservableList getFilteredSupplierList(); + ObservableList getFilteredDelivererList(); + ObservableList getFilteredPetList(); + ObservableList getFilteredOrderList(); + ObservableList getFilteredMainList(); + ObservableList getFilteredCurrList(); /** * 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 updateFilteredBuyerList(Predicate predicate); + void updateFilteredSupplierList(Predicate predicate); + void updateFilteredDelivererList(Predicate predicate); + void updateFilteredPetList(Predicate predicate); + void updateFilteredOrderList(Predicate predicate); + + /** + * Gets orders from a given buyer. + * @param buyer The given buyer. + * @return The list of orders. + */ + List getOrdersFromBuyer(Buyer buyer); + + /** + * Gets orders from a given deliverer. + * @param deliverer The given deliverer. + * @return The list of orders. + */ + List getOrdersFromDeliverer(Deliverer deliverer); + + /** + * Gets pets from a given supplier. + * @param supplier The given supplier. + * @return The list of pets. + */ + List getPetsFromSupplier(Supplier supplier); + + /** + * Clears the curr display list + */ + void clearCurrList(); + + /** + * Switches the current displayed list to buyer list. + */ + void switchToBuyerList(); + + /** + * Switches the current displayed list to supplier list. + */ + void switchToSupplierList(); + + /** + * Switches the current displayed list to deliverer list. + */ + void switchToDelivererList(); + + /** + * Switches the current displayed list to order list. + */ + void switchToOrderList(); + + /** + * Switches the current displayed list to pet list. + */ + void switchToPetList(); + + /** + * Switches the current displayed list to main list. + */ + void switchToMainList(); + + /** + * Sets the current list to the list of orders of a buyer. + */ + void checkBuyerOrder(Buyer buyer); + + /** + * Sets the current list to the list of pets of a supplier. + */ + void checkSupplierPet(Supplier supplier); + + /** + * Sets the current list to the list of orders of a deliverer. + */ + void checkDelivererOrder(Deliverer deliverer); + + /** + * Sets the current list to the show the buyer of an order. + */ + void checkBuyerOfOrder(Order order); + + /** + * Sets the current list to show the supplier of a pet. + */ + void checkSupplierOfPet(Pet pet); + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 86c1df298d7..f63991f9bae 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,24 +4,57 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.time.format.DateTimeFormatter; +import java.util.Comparator; +import java.util.List; import java.util.function.Predicate; import java.util.logging.Logger; +import javafx.collections.FXCollections; import javafx.collections.ObservableList; 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.order.Order; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.MasterList; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; /** * Represents the in-memory model of the address book data. */ public class ModelManager implements Model { + + //@@author Hongyi6328-reused + //Reused from https://github.com/RussellDash332/ip/blob/master/src/main/java/stashy/parser/Parser.java + //with minor modification, it is a pretty good way to organise and extend the acceptable date formats. + public static final String[] ACCEPTABLE_DATE_FORMATS = new String[]{ + "MMM dd yyyy", + "dd/MM/yyyy", + "yyyy/MM/dd", + "yyyy-MM-dd", + "dd MMM yyyy", + "dd MMM yyyy", + "MMM dd, yyyy", + "MMM dd, yyyy" + }; + + public static final String PREFERRED_DATE_FORMAT = "yyyy-MM-dd"; + + public static final DateTimeFormatter PREFERRED_FORMATTER = DateTimeFormatter.ofPattern(PREFERRED_DATE_FORMAT); private static final Logger logger = LogsCenter.getLogger(ModelManager.class); private final AddressBook addressBook; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; + private final FilteredList filteredBuyers; + private final FilteredList filteredSuppliers; + private final FilteredList filteredDeliverers; + private final FilteredList filteredPets; + private final FilteredList filteredOrders; + private final MasterList filteredAll; + private FilteredList filteredCurrList; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -33,7 +66,14 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredBuyers = new FilteredList<>(this.addressBook.getBuyerList()); + filteredSuppliers = new FilteredList<>(this.addressBook.getSupplierList()); + filteredDeliverers = new FilteredList<>(this.addressBook.getDelivererList()); + filteredPets = new FilteredList<>(this.addressBook.getPetList()); + filteredOrders = new FilteredList<>(this.addressBook.getOrderList()); + filteredAll = new MasterList(); + collect(); + switchToMainList(); } public ModelManager() { @@ -88,44 +128,356 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); + public boolean hasBuyer(Buyer buyer) { + requireNonNull(buyer); + return addressBook.hasBuyer(buyer); + } + + @Override + public boolean hasSupplier(Supplier supplier) { + requireNonNull(supplier); + return addressBook.hasSupplier(supplier); + } + + @Override + public boolean hasDeliverer(Deliverer deliverer) { + requireNonNull(deliverer); + return addressBook.hasDeliverer(deliverer); + } + + @Override + public boolean hasPet(Pet pet) { + requireNonNull(pet); + return addressBook.hasPet(pet); + } + + @Override + public boolean hasOrder(Order order) { + requireNonNull(order); + return addressBook.hasOrder(order); + } + + @Override + public void deleteBuyer(Buyer target) { + addressBook.removeBuyer(target); + collect(); + } + + @Override + public void deleteSupplier(Supplier target) { + addressBook.removeSupplier(target); + collect(); + } + + @Override + public void deleteDeliverer(Deliverer target) { + addressBook.removeDeliverer(target); + collect(); + } + + @Override + public void deletePet(Pet target) { + addressBook.removePet(target); + collect(); + } + + @Override + public void deleteOrder(Order target) { + addressBook.removeOrder(target); + collect(); + } + + @Override + public void addBuyer(Buyer buyer) { + addressBook.addBuyer(buyer); + updateFilteredBuyerList(PREDICATE_SHOW_ALL_BUYERS); + } + + @Override + public void addSupplier(Supplier supplier) { + addressBook.addSupplier(supplier); + updateFilteredSupplierList(PREDICATE_SHOW_ALL_SUPPLIERS); + } + + @Override + public void addDeliverer(Deliverer deliverer) { + addressBook.addDeliverer(deliverer); + updateFilteredDelivererList(PREDICATE_SHOW_ALL_DELIVERERS); } @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); + public void addPet(Pet pet) { + addressBook.addPet(pet); + updateFilteredPetList(PREDICATE_SHOW_ALL_PETS); } @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public void addOrder(Order order) { + addressBook.addOrder(order); + updateFilteredOrderList(PREDICATE_SHOW_ALL_ORDERS); } @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public void setBuyer(Buyer target, Buyer editedBuyer) { + requireAllNonNull(target, editedBuyer); - addressBook.setPerson(target, editedPerson); + addressBook.setBuyer(target, editedBuyer); + List orderFromId = addressBook.getOrderFromId(target.getOrderIds()); + for (Order order: orderFromId) { + order.setBuyer(editedBuyer); + } + collect(); + } + + @Override + public void setSupplier(Supplier target, Supplier editedSupplier) { + requireAllNonNull(target, editedSupplier); + + addressBook.setSupplier(target, editedSupplier); + List petFromId = addressBook.getPetFromId(target.getPetIds()); + for (Pet pet: petFromId) { + pet.setSupplier(editedSupplier); + } + collect(); + } + + @Override + public void setDeliverer(Deliverer target, Deliverer editedDeliverer) { + requireAllNonNull(target, editedDeliverer); + + addressBook.setDeliverer(target, editedDeliverer); + + collect(); + } + + @Override + public void setPet(Pet target, Pet editedPet) { + requireAllNonNull(target, editedPet); + + addressBook.setPet(target, editedPet); + collect(); } - //=========== Filtered Person List Accessors ============================================================= + @Override + public void setOrder(Order target, Order editedOrder) { + requireAllNonNull(target, editedOrder); + + addressBook.setOrder(target, editedOrder); + collect(); + } + + @Override + public void sortBuyer(Comparator comparator) { + requireNonNull(comparator); + addressBook.sortBuyer(comparator); + collect(); + } + + @Override + public void sortSupplier(Comparator comparator) { + requireNonNull(comparator); + addressBook.sortSupplier(comparator); + collect(); + } + + @Override + public void sortDeliverer(Comparator comparator) { + requireNonNull(comparator); + addressBook.sortDeliverer(comparator); + collect(); + } + + @Override + public void sortOrder(Comparator comparator) { + requireNonNull(comparator); + addressBook.sortOrder(comparator); + collect(); + } + + @Override + public void sortPet(Comparator comparator) { + requireNonNull(comparator); + addressBook.sortPet(comparator); + collect(); + } + + //=========== Filtered Objects List Accessors ============================================================= + /** + * Returns an ObservableList of buyers in the filteredPersons list. + * + * @return ObservableList of buyers. + */ + public ObservableList getFilteredBuyerList() { + return filteredBuyers; + } + + /** + * Returns an ObservableList of suppliers in the filteredPersons list. + * + * @return ObservableList of suppliers. + */ + public ObservableList getFilteredSupplierList() { + return filteredSuppliers; + } /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} + * Returns an ObservableList of deliverers in the filteredPersons list. + * + * @return ObservableList of deliverers. */ + public ObservableList getFilteredDelivererList() { + return filteredDeliverers; + } + + @Override + public ObservableList getFilteredPetList() { + return filteredPets; + } + + @Override + public ObservableList getFilteredOrderList() { + return filteredOrders; + } + @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; + public ObservableList getFilteredMainList() { + collect(); + return filteredAll.getMasterList(); } @Override - public void updateFilteredPersonList(Predicate predicate) { + public ObservableList getFilteredCurrList() { + + @SuppressWarnings("unchecked") + ObservableList res = (ObservableList) filteredCurrList; + + return res; + } + + @Override + public void updateFilteredBuyerList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredBuyers.setPredicate(predicate); + collect(); + } + + @Override + public void updateFilteredSupplierList(Predicate predicate) { + requireNonNull(predicate); + filteredSuppliers.setPredicate(predicate); + collect(); + } + + @Override + public void updateFilteredDelivererList(Predicate predicate) { + requireNonNull(predicate); + filteredDeliverers.setPredicate(predicate); + collect(); + } + + @Override + public void updateFilteredPetList(Predicate predicate) { + requireNonNull(predicate); + filteredPets.setPredicate(predicate); + collect(); + } + + @Override + public void updateFilteredOrderList(Predicate predicate) { + requireNonNull(predicate); + filteredOrders.setPredicate(predicate); + collect(); + } + + @Override + public List getOrdersFromBuyer(Buyer buyer) { + requireNonNull(buyer); + return addressBook.getOrderFromId(buyer.getOrderIds()); + } + + @Override + public List getOrdersFromDeliverer(Deliverer deliverer) { + requireNonNull(deliverer); + return addressBook.getOrderFromId(deliverer.getOrders()); + } + + @Override + public List getPetsFromSupplier(Supplier supplier) { + requireNonNull(supplier); + return addressBook.getPetFromId(supplier.getPetIds()); + } + + @Override + public void clearCurrList() { + filteredAll.clear(); + filteredCurrList.clear(); + } + + @Override + public void switchToBuyerList() { + filteredCurrList = filteredBuyers; + } + + @Override + public void switchToSupplierList() { + filteredCurrList = filteredSuppliers; + } + + @Override + public void switchToDelivererList() { + filteredCurrList = filteredDeliverers; + } + + @Override + public void switchToOrderList() { + filteredCurrList = filteredOrders; + } + + @Override + public void switchToPetList() { + filteredCurrList = filteredPets; + } + + @Override + public void switchToMainList() { + filteredCurrList = new FilteredList<>(filteredAll.getMasterList()); + } + + @Override + public void checkBuyerOrder(Buyer buyer) { + ObservableList orders = FXCollections.observableArrayList(getOrdersFromBuyer(buyer)); + filteredCurrList = new FilteredList<>(orders); + } + + @Override + public void checkSupplierPet(Supplier supplier) { + ObservableList pets = FXCollections.observableArrayList(getPetsFromSupplier(supplier)); + filteredCurrList = new FilteredList<>(pets); + } + + @Override + public void checkDelivererOrder(Deliverer deliverer) { + ObservableList orders = FXCollections.observableArrayList(getOrdersFromDeliverer(deliverer)); + filteredCurrList = new FilteredList<>(orders); + } + + @Override + public void checkBuyerOfOrder(Order order) { + ObservableList buyers = FXCollections.observableArrayList(order.getBuyer()); + filteredCurrList = new FilteredList<>(buyers); + } + + @Override + public void checkSupplierOfPet(Pet pet) { + ObservableList suppliers = FXCollections.observableArrayList(pet.getSupplier()); + filteredCurrList = new FilteredList<>(suppliers); + } + + private void collect() { + filteredAll.clear(); + filteredAll.addAll(filteredBuyers); + filteredAll.addAll(filteredSuppliers); + filteredAll.addAll(filteredDeliverers); } @Override @@ -144,7 +496,11 @@ public boolean equals(Object obj) { ModelManager other = (ModelManager) obj; return addressBook.equals(other.addressBook) && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); + && filteredBuyers.equals(other.filteredBuyers) + && filteredSuppliers.equals(other.filteredSuppliers) + && filteredDeliverers.equals(other.filteredDeliverers) + && filteredPets.equals(other.filteredPets) + && filteredOrders.equals(other.filteredOrders); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..fe1c94290a8 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,10 +1,14 @@ package seedu.address.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.order.Order; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; /** - * Unmodifiable view of an address book + * Unmodifiable view of an address book. */ public interface ReadOnlyAddressBook { @@ -12,6 +16,15 @@ public interface ReadOnlyAddressBook { * Returns an unmodifiable view of the persons list. * This list will not contain any duplicate persons. */ - ObservableList getPersonList(); + ObservableList getBuyerList(); + + ObservableList getSupplierList(); + + ObservableList getDelivererList(); + + ObservableList getPetList(); + + ObservableList getOrderList(); + } diff --git a/src/main/java/seedu/address/model/order/AdditionalRequests.java b/src/main/java/seedu/address/model/order/AdditionalRequests.java new file mode 100644 index 00000000000..fe58198e3f4 --- /dev/null +++ b/src/main/java/seedu/address/model/order/AdditionalRequests.java @@ -0,0 +1,71 @@ +package seedu.address.model.order; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * Represents the additional requests of an Order. + */ +public class AdditionalRequests { + + public static final String MESSAGE_CONSTRAINTS = + "Additional request should only contain alphanumeric characters and spaces, and it should not be blank"; + + private final List additionalRequests = new ArrayList<>(); + + /** + * Constructs the AdditionalRequests object with an array. + * + * @param descriptions The string descriptions of these additional requests. + */ + public AdditionalRequests(String... descriptions) { + requireNonNull(descriptions); + additionalRequests.addAll(Arrays.asList(descriptions)); + } + + /** + * Constructs the AdditionalRequests object with an array. + * + * @param descriptions The string descriptions of these additional requests. + */ + public AdditionalRequests(Collection descriptions) { + additionalRequests.addAll(descriptions); + } + + public List getAdditionalRequestsToString() { + return additionalRequests; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof AdditionalRequests) { + AdditionalRequests otherAdditionalRequests = (AdditionalRequests) other; + return additionalRequests.equals(otherAdditionalRequests.additionalRequests); + } else { + return false; + } + } + + @Override + public int hashCode() { + return additionalRequests.hashCode(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + int i = 1; + for (String description : additionalRequests) { + builder.append(i).append(". ").append(description).append(System.lineSeparator()); + i++; + } + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/order/Order.java b/src/main/java/seedu/address/model/order/Order.java new file mode 100644 index 00000000000..eb967d91dfa --- /dev/null +++ b/src/main/java/seedu/address/model/order/Order.java @@ -0,0 +1,262 @@ +package seedu.address.model.order; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDate; +import java.util.Objects; + +import seedu.address.commons.core.index.UniqueId; +import seedu.address.commons.core.index.UniqueIdGenerator; +import seedu.address.model.person.Buyer; + +/** + * Represents an order placed by a Buyer. + */ +public class Order { + + private static final UniqueIdGenerator ORDER_ID_GENERATOR = new UniqueIdGenerator(); + + private final UniqueId id; + private Buyer buyer; + private final PriceRange requestedPriceRange; + private final Request request; + private final AdditionalRequests additionalRequests; + private final LocalDate byDate; + private final Price settledPrice; + private final OrderStatus status; + + /** + * Constructs an Order object. + * + * @param buyer The buyer who initiates the request. + * @param requestedPriceRange The acceptable price range during negotiation. + * @param request The description of the request, that is, what kind of pet the buyer wants. + * @param additionalRequests Some other requests in string. + * @param byDate The date before which the order should be dealt. + * @param settledPrice The settled final price. + * @param status Whether this order is under negotiation, or finished, or is being delivered, etc. + */ + public Order(Buyer buyer, + PriceRange requestedPriceRange, + Request request, + AdditionalRequests additionalRequests, + LocalDate byDate, + Price settledPrice, + OrderStatus status) { + requireAllNonNull(status); + UniqueId currId = ORDER_ID_GENERATOR.next(); + while (UniqueIdGenerator.storedIdOrderContains(currId)) { + currId = ORDER_ID_GENERATOR.next(); + } + this.id = currId; + this.buyer = buyer; + this.requestedPriceRange = requestedPriceRange; + this.request = request; + this.additionalRequests = additionalRequests; + this.byDate = byDate; + this.settledPrice = settledPrice; + this.status = status; + } + + /** + * Constructs an Order object. + * The status is by default Pending. + * + * @param buyer The buyer who initiates the request. + * @param requestedPriceRange The acceptable price range during negotiation. + * @param request The description of the request, that is, what kind of pet the buyer wants. + * @param additionalRequests Some other requests in string. + * @param byDate The date before which the order should be dealt. + * @param settledPrice The settled final price. + */ + public Order(Buyer buyer, + PriceRange requestedPriceRange, + Request request, + AdditionalRequests additionalRequests, + LocalDate byDate, + Price settledPrice) { + this(buyer, + requestedPriceRange, + request, + additionalRequests, + byDate, + settledPrice, + OrderStatus.PENDING); + } + + /** + * Constructs an Order object. + * + * @param buyer The buyer who initiates the request. + * @param requestedPriceRange The acceptable price range during negotiation. + * @param request The description of the request, that is, what kind of pet the buyer wants. + * @param additionalRequests Some other requests in string. + * @param byDate The date before which the order should be dealt. + * @param settledPrice The settled final price. + * @param status Whether this order is under negotiation, or finished, or is being delivered, etc. + * @param uniqueId The assigned uniqueId for this order. + */ + public Order(Buyer buyer, + PriceRange requestedPriceRange, + Request request, + AdditionalRequests additionalRequests, + LocalDate byDate, + Price settledPrice, + OrderStatus status, + UniqueId uniqueId) { + requireAllNonNull(status); + UniqueIdGenerator.addToStoredIdOrder(uniqueId); + this.id = uniqueId; + this.buyer = buyer; + this.requestedPriceRange = requestedPriceRange; + this.request = request; + this.additionalRequests = additionalRequests; + this.byDate = byDate; + this.settledPrice = settledPrice; + this.status = status; + } + + /** + * Gets the buyer. + * + * @return The buyer of this order. + */ + public Buyer getBuyer() { + return buyer; + } + + /** + * Gets the price range of the order. + * + * @return The price range. + */ + public PriceRange getRequestedPriceRange() { + return requestedPriceRange; + } + + /** + * Gets the request of the order. + * + * @return The request. + */ + public Request getRequest() { + return request; + } + + /** + * Gets the additional requests of the order. + * + * @return The additional request. + */ + public AdditionalRequests getAdditionalRequests() { + return additionalRequests; + } + + /** + * Gets the date of the order. + * + * @return The date in {@code LocalDate}. + */ + public LocalDate getByDate() { + return byDate; + } + + /** + * Gets the settled price of the order. + * + * @return The price. + */ + public Price getSettledPrice() { + return settledPrice; + } + + /** + * Gets the status of the order. + * + * @return The status. + */ + public OrderStatus getOrderStatus() { + return status; + } + + /** + * Gets the unique ID of the order. + * + * @return The ID. + */ + public UniqueId getId() { + return id; + } + + public void setBuyer(Buyer buyer) { + this.buyer = buyer; + } + + /** + * Updates the price range changed during negotiation. + * + * @param upperBound The price is not greater than it. + * @param lowerBound The price is not smaller than it. + */ + public void updateRequestedPriceRange(Price upperBound, Price lowerBound) { + requestedPriceRange.updatePriceRange(upperBound, lowerBound); + } + + /** + * Compares an order with another order in default way in terms of the due date. + * @param order The other order being compared. + * @return The method returns 0 if the order and the other order has the same due date. + * A value less than 0 is returned if the order has earlier due date than the other order, + * and a value greater than 0 if the order has later due date than the other order. + */ + public int compareTo(Order order) { + return this.byDate.compareTo(order.byDate); + } + + /** + * Returns true if the two uniqueId matches. + */ + public boolean hasId(UniqueId id) { + return this.id.equals(id); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof Order) { + Order otherOrder = (Order) other; + return id.equals(otherOrder.id); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + + StringBuilder builder = new StringBuilder(); + builder.append("Requested Price Range: ").append(getRequestedPriceRange()) + .append(System.lineSeparator()) + .append("Settled price: ").append(getSettledPrice()) + .append(System.lineSeparator()) + .append("Status: ").append(getOrderStatus()) + .append(System.lineSeparator()) + .append("Process order by: ").append(getByDate()) + .append(System.lineSeparator()) + .append("=== Request ===").append(System.lineSeparator()) + .append(request.toString()).append(System.lineSeparator()) + .append("==========").append(System.lineSeparator()) + .append("=== Additional Requests ===").append(System.lineSeparator()) + .append(additionalRequests.toString()).append(System.lineSeparator()) + .append("=========="); + return builder.toString(); + + } + +} diff --git a/src/main/java/seedu/address/model/order/OrderStatus.java b/src/main/java/seedu/address/model/order/OrderStatus.java new file mode 100644 index 00000000000..8cc2b19c326 --- /dev/null +++ b/src/main/java/seedu/address/model/order/OrderStatus.java @@ -0,0 +1,42 @@ +package seedu.address.model.order; + +import java.util.Arrays; + +/** + * Represents that current status of an order. It can only be of three categories - Pending, Negotiating or Delivering. + */ +public enum OrderStatus { + PENDING("Pending"), + NEGOTIATING("Negotiating"), + DELIVERING("Delivering"); + + public static final String MESSAGE_CONSTRAINTS = + "Order status should be either 'Pending', 'Negotiating', or 'Delivering'."; + private final String status; + + OrderStatus(String status) { + this.status = status; + } + + public String getStatus() { + return status; + } + + @Override + public String toString() { + return status; + } + + /** + * Checks if an order status is valid. + * + * @param input A string indicating the order status. + * @return True if the input status is a valid status, false otherwise. + */ + public static boolean isValidOrderStatus(String input) { + return Arrays + .stream(OrderStatus.class.getEnumConstants()) + .anyMatch(x -> x.toString().equals(input)); + } + +} diff --git a/src/main/java/seedu/address/model/order/Price.java b/src/main/java/seedu/address/model/order/Price.java new file mode 100644 index 00000000000..b2925286185 --- /dev/null +++ b/src/main/java/seedu/address/model/order/Price.java @@ -0,0 +1,73 @@ +package seedu.address.model.order; + +/** + * Represents the final settled price of an order. + */ +public class Price implements Comparable { + + public static final double NOT_APPLICABLE_PRICE = -1; + public static final String MESSAGE_USAGE = + "The price should be a non-negative decimal number, such as 0.3, 9.8 etc.\n" + + "If you have not decided the price yet, enter " + + NOT_APPLICABLE_PRICE + + " to indicate a non-applicable price"; + + private double price; + + /** + * Creates a Price. + */ + public Price(double price) { + this.price = price; + } + + public static Price getNotApplicablePrice() { + return new Price(NOT_APPLICABLE_PRICE); + } + + public boolean isNotApplicablePrice() { + return this.price == NOT_APPLICABLE_PRICE; + } + + public static boolean isNotApplicablePrice(Price price) { + return price.price == NOT_APPLICABLE_PRICE; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof Price) { + Price otherPrice = (Price) other; + return price == otherPrice.price; + } else { + return false; + } + } + + @Override + public int hashCode() { + return Double.hashCode(price); + } + + @Override + public String toString() { + String outputString = price == NOT_APPLICABLE_PRICE + ? "N/A" + : Double.toString(price); + return "Price settled: " + outputString; + } + + @Override + public int compareTo(Price other) { + return Double.compare(this.price, other.price); + } +} diff --git a/src/main/java/seedu/address/model/order/PriceRange.java b/src/main/java/seedu/address/model/order/PriceRange.java new file mode 100644 index 00000000000..fcbaf0c55ae --- /dev/null +++ b/src/main/java/seedu/address/model/order/PriceRange.java @@ -0,0 +1,123 @@ +package seedu.address.model.order; + +import java.util.Objects; + +/** + * Represents the price range (lower bound, upper bound) of an order during negotiation. + */ +public class PriceRange implements Comparable { + + public static final String DELIMITER = ","; + public static final String MESSAGE_USAGE = + "The price range must be two non-negative decimal numbers, separated by a comma. " + + "For example, 0.8,2.1. The left should be smaller. " + + "If you have not decided one bound, you can enter " + Price.NOT_APPLICABLE_PRICE + + " to indicate non-applicable price. " + + "For example, 0.7,-1 means the price is at least 0.7 but not bounded above. "; + public static final String MESSAGE_CONSTRAINT = + "The settled price must be within the range! For example, -1 is within any range; 5 is within (-1, 6); " + + "7 is within (2, -1)"; + public static final int LOWER_THAN_RANGE = -1; + public static final int WITHIN_RANGE = 0; + public static final int HIGHER_THAN_RANGE = 1; + + private Price upperBound; + private Price lowerBound; + + /** + * Constructs a PriceRange object. + * + * @param lowerBound The bound that the final price is expected not to be smaller than. + * @param upperBound The bound that the final price is expected not to be greater than + */ + public PriceRange(Price lowerBound, Price upperBound) { + this.upperBound = upperBound; + this.lowerBound = lowerBound; + } + + public Price getUpperBound() { + return upperBound; + } + + public Price getLowerBound() { + return lowerBound; + } + + public void setUpperBound(Price upperBound) { + this.upperBound = upperBound; + } + + public void setLowerBound(Price lowerBound) { + this.lowerBound = lowerBound; + } + + /** + * Updates both bounds. + * + * @param lowerBound The bound that the final price is expected not to be smaller than. + * @param upperBound The bound that the final price is expected not to be greater than + */ + public void updatePriceRange(Price lowerBound, Price upperBound) { + this.upperBound = upperBound; + this.lowerBound = lowerBound; + } + + + /** + * Checks if a price is within the price range. + * + * @param price The price to be checked. + * @return {@code WITHIN_RANGE} if the price passed is within the range, + * {@code LOWER_THAN_RANGE}, {@code HIGHER_THAN_RANGE} respectively. + */ + public int comparePrice(Price price) { + if (price.isNotApplicablePrice()) { + return WITHIN_RANGE; + } + if (lowerBound.isNotApplicablePrice()) { + if (upperBound.isNotApplicablePrice()) { + return WITHIN_RANGE; + } else { + return price.compareTo(upperBound) <= 0 ? WITHIN_RANGE : HIGHER_THAN_RANGE; + } + } else { + if (upperBound.isNotApplicablePrice()) { + return price.compareTo(lowerBound) >= 0 ? WITHIN_RANGE : LOWER_THAN_RANGE; + } else { + if (price.compareTo(lowerBound) >= 0) { + return price.compareTo(upperBound) <= 0 ? WITHIN_RANGE : HIGHER_THAN_RANGE; + } else { + return LOWER_THAN_RANGE; + } + } + } + } + + @Override + public String toString() { + return "Price range: " + lowerBound.getPrice() + " , " + upperBound.getPrice(); + } + + @Override + public int hashCode() { + return Objects.hash(upperBound, lowerBound); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof PriceRange) { + PriceRange otherRange = (PriceRange) other; + return lowerBound.equals(otherRange.getLowerBound()) + && upperBound.equals(otherRange.getUpperBound()); + } else { + return false; + } + } + + @Override + public int compareTo(PriceRange o) { + return this.lowerBound.compareTo(o.lowerBound); + } +} diff --git a/src/main/java/seedu/address/model/order/Request.java b/src/main/java/seedu/address/model/order/Request.java new file mode 100644 index 00000000000..8915706f610 --- /dev/null +++ b/src/main/java/seedu/address/model/order/Request.java @@ -0,0 +1,100 @@ +package seedu.address.model.order; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_AGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_COLOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_COLOR_PATTERN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ORDER_SPECIES; + +import java.util.Objects; + +import seedu.address.model.pet.Age; +import seedu.address.model.pet.Color; +import seedu.address.model.pet.ColorPattern; +import seedu.address.model.pet.Species; + +/** + * Contains some properties of a pet desired in an order. + */ +public class Request { + + public static final String MESSAGE_USAGE = "The usage of a request is as follows: \n" + + "add-r " + + PREFIX_ORDER_SPECIES + "SPECIES " + + PREFIX_ORDER_AGE + "AGE " + + PREFIX_ORDER_COLOR + "COLOR " + + PREFIX_ORDER_COLOR_PATTERN + "COLOR_PATTERN \n" + + Age.MESSAGE_USAGE; + + private final Age requestedAge; + private final Color requestedColor; + private final ColorPattern requestedColorPattern; + private final Species requestedSpecies; + + /** + * Constructs a {@code request}. + * + * @param requestedAge Age of the pet. + * @param requestedColor Color of the pet. + * @param requestedColorPattern Color pattern of the pet. + * @param requestedSpecies Species the pet belongs to. + */ + public Request(Age requestedAge, + Color requestedColor, + ColorPattern requestedColorPattern, + Species requestedSpecies) { + this.requestedAge = requestedAge; + this.requestedColor = requestedColor; + this.requestedColorPattern = requestedColorPattern; + this.requestedSpecies = requestedSpecies; + } + + public Age getRequestedAge() { + return requestedAge; + } + + public Color getRequestedColor() { + return requestedColor; + } + + public ColorPattern getRequestedColorPattern() { + return requestedColorPattern; + } + + public Species getRequestedSpecies() { + return requestedSpecies; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof Request) { + Request otherRequest = (Request) other; + return requestedAge.equals(otherRequest.getRequestedAge()) + && requestedColor.equals(otherRequest.getRequestedColor()) + && requestedColorPattern.equals(otherRequest.getRequestedColorPattern()) + && requestedSpecies.equals(otherRequest.getRequestedSpecies()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(requestedAge, requestedColor, requestedColorPattern, requestedSpecies); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Pet age: ").append(getRequestedAge()) + .append(System.lineSeparator()) + .append("Color: ").append(getRequestedColor()) + .append(System.lineSeparator()) + .append("Pattern: ").append(getRequestedColorPattern()) + .append(System.lineSeparator()) + .append("Species: ").append(getRequestedSpecies()); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/order/UniqueOrderList.java b/src/main/java/seedu/address/model/order/UniqueOrderList.java new file mode 100644 index 00000000000..df999aa2c01 --- /dev/null +++ b/src/main/java/seedu/address/model/order/UniqueOrderList.java @@ -0,0 +1,158 @@ +package seedu.address.model.order; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.core.index.UniqueId; +import seedu.address.model.order.exceptions.DuplicateOrderException; +import seedu.address.model.order.exceptions.OrderNotFoundException; + +/** + * A list of orders that enforces uniqueness between its elements and does not allow nulls. + * An order is considered unique by comparing using {@code Order#isSameOrder(Order)}. As such, adding and updating of + * orders uses Order#isSameOrder(Order) for equality so as to ensure that the order being added or updated is + * unique in terms of identity in the UniqueOrderList. However, the removal of an order uses Order#equals(Object) so + * as to ensure that the order with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + */ +public class UniqueOrderList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent Order as the given argument. + */ + public boolean contains(Order toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + /** + * Adds an Order to the list. + * The Order must not already exist in the list. + */ + public void add(Order toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateOrderException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the Order {@code target} in the list with {@code editedOrder}. + * {@code target} must exist in the list. + * The Order identity of {@code editedOrder} must not be the same as another existing Order in the list. + */ + public void setOrder(Order target, Order editedOrder) { + requireAllNonNull(target, editedOrder); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new OrderNotFoundException(); + } + + if (!target.equals(editedOrder) && contains(editedOrder)) { + throw new DuplicateOrderException(); + } + + internalList.set(index, editedOrder); + } + + /** + * Removes the equivalent Order from the list. + * The Order must exist in the list. + */ + public void remove(Order toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new OrderNotFoundException(); + } + } + + public void setOrders(UniqueOrderList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code Orders}. + * {@code Orders} must not contain duplicate Orders. + */ + public void setOrders(List orders) { + requireAllNonNull(orders); + if (!ordersAreUnique(orders)) { + throw new DuplicateOrderException(); + } + + internalList.setAll(orders); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + /** + * Gets the list of orders from list of ids. + */ + public List getOrdersFromId(List ids) { + return internalList.stream() + .filter(order -> ids.stream() + .anyMatch(order::hasId)) + .collect(Collectors.toList()); + } + + /** + * Sorts the list using the specified comparator. + * @param comparator The specified comparator. + */ + public void sort(Comparator comparator) { + internalList.sort(comparator); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueOrderList // instanceof handles nulls + && internalList.equals(((UniqueOrderList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true iff {@code orders} contains only unique Orders. + */ + private boolean ordersAreUnique(List orders) { + for (int i = 0; i < orders.size() - 1; i++) { + for (int j = i + 1; j < orders.size(); j++) { + if (orders.get(i).equals(orders.get(j))) { + return false; + } + } + } + return true; + } + +} diff --git a/src/main/java/seedu/address/model/order/exceptions/DuplicateOrderException.java b/src/main/java/seedu/address/model/order/exceptions/DuplicateOrderException.java new file mode 100644 index 00000000000..818c384f5f2 --- /dev/null +++ b/src/main/java/seedu/address/model/order/exceptions/DuplicateOrderException.java @@ -0,0 +1,11 @@ +package seedu.address.model.order.exceptions; + +/** + * Signals that the operation will result in duplicate Orders (Orders are considered duplicates if they have the same + * identity). + */ +public class DuplicateOrderException extends RuntimeException { + public DuplicateOrderException() { + super("Operation would result in duplicate orders"); + } +} diff --git a/src/main/java/seedu/address/model/order/exceptions/OrderNotFoundException.java b/src/main/java/seedu/address/model/order/exceptions/OrderNotFoundException.java new file mode 100644 index 00000000000..b3091c7208c --- /dev/null +++ b/src/main/java/seedu/address/model/order/exceptions/OrderNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.address.model.order.exceptions; + +/** + * Signals that the operation is unable to find the specified order. + */ +public class OrderNotFoundException extends RuntimeException { +} diff --git a/src/main/java/seedu/address/model/order/predicates/AdditionalRequestPredicate.java b/src/main/java/seedu/address/model/order/predicates/AdditionalRequestPredicate.java new file mode 100644 index 00000000000..9e0e0ecff96 --- /dev/null +++ b/src/main/java/seedu/address/model/order/predicates/AdditionalRequestPredicate.java @@ -0,0 +1,41 @@ +package seedu.address.model.order.predicates; + +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import seedu.address.model.order.Order; + +/** + * Tests that a {@code Order}'s {@code AdditionalRequest} matches any of the keywords given. + */ +public class AdditionalRequestPredicate implements Predicate { + private final List keywords; + + /** + * Creates a {@code AdditionalRequestPredicate} to matche any of the keywords given. + */ + public AdditionalRequestPredicate(List keywords) { + keywords = keywords.stream().map(String::toLowerCase).collect(Collectors.toList()); + this.keywords = keywords; + } + + @Override + public boolean test(T order) { + List additionalRequests = order.getAdditionalRequests().getAdditionalRequestsToString().stream() + .map(String::toLowerCase).collect(Collectors.toList()); + Set result = additionalRequests.stream() + .distinct() + .filter(keywords::contains) + .collect(Collectors.toSet()); + return result.size() > 0; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AdditionalRequestPredicate // instanceof handles nulls + && keywords.equals(((AdditionalRequestPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/order/predicates/OrderStatusPredicate.java b/src/main/java/seedu/address/model/order/predicates/OrderStatusPredicate.java new file mode 100644 index 00000000000..895e4547652 --- /dev/null +++ b/src/main/java/seedu/address/model/order/predicates/OrderStatusPredicate.java @@ -0,0 +1,29 @@ +package seedu.address.model.order.predicates; + +import java.util.function.Predicate; + +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderStatus; + +/** + * Tests that a {@code Order}'s {@code OrderStatus} matches any of the keywords given. + */ +public class OrderStatusPredicate implements Predicate { + private final OrderStatus orderStatus; + + public OrderStatusPredicate(OrderStatus orderStatus) { + this.orderStatus = orderStatus; + } + + @Override + public boolean test(T order) { + return order.getOrderStatus().equals(orderStatus); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OrderStatusPredicate // instanceof handles nulls + && orderStatus.equals(((OrderStatusPredicate) other).orderStatus)); // state check + } +} diff --git a/src/main/java/seedu/address/model/order/predicates/PriceRangePredicate.java b/src/main/java/seedu/address/model/order/predicates/PriceRangePredicate.java new file mode 100644 index 00000000000..4b87e4c90ea --- /dev/null +++ b/src/main/java/seedu/address/model/order/predicates/PriceRangePredicate.java @@ -0,0 +1,50 @@ +package seedu.address.model.order.predicates; + +import java.util.function.Predicate; + +import seedu.address.model.order.Order; +import seedu.address.model.order.Price; +import seedu.address.model.order.PriceRange; + +/** + * Tests that a {@code Order}'s {@code PriceRange} matches any of the keywords given. + */ +public class PriceRangePredicate implements Predicate { + private final Price lowerBound; + private final Price upperBound; + + /** + * Constructs a PriceRangePredicate. + * + * @param lowerBound The lower bound Price. + * @param upperBound The upper bound Price. + */ + public PriceRangePredicate(Price lowerBound, Price upperBound) { + this.lowerBound = lowerBound; + this.upperBound = upperBound; + } + + @Override + public boolean test(T order) { + Price orderLowerBound = order.getRequestedPriceRange().getLowerBound(); + Price orderUpperBound = order.getRequestedPriceRange().getUpperBound(); + PriceRange thisRange = new PriceRange(lowerBound, upperBound); + boolean isThisLowerNA = lowerBound.isNotApplicablePrice(); + boolean isThisUpperNA = upperBound.isNotApplicablePrice(); + boolean isOtherLowerNA = orderLowerBound.isNotApplicablePrice(); + boolean isOtherUpperNA = orderUpperBound.isNotApplicablePrice(); + boolean isAboveLowerBound = thisRange.comparePrice(orderLowerBound) == PriceRange.WITHIN_RANGE + && ((!isOtherLowerNA) || (isThisLowerNA) || (lowerBound.getPrice() == 0)); + boolean isBelowUpperBound = thisRange.comparePrice(orderUpperBound) == PriceRange.WITHIN_RANGE + && ((!isOtherUpperNA) || (isThisUpperNA)); + return isAboveLowerBound && isBelowUpperBound; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PriceRangePredicate // instanceof handles nulls + && lowerBound.equals(((PriceRangePredicate) other).lowerBound) + && upperBound.equals(((PriceRangePredicate) other).upperBound)); // state check + } +} diff --git a/src/main/java/seedu/address/model/order/predicates/UniqueOrderIdPredicate.java b/src/main/java/seedu/address/model/order/predicates/UniqueOrderIdPredicate.java new file mode 100644 index 00000000000..e81a70c7995 --- /dev/null +++ b/src/main/java/seedu/address/model/order/predicates/UniqueOrderIdPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.order.predicates; + +import java.util.function.Predicate; + +import seedu.address.commons.core.index.UniqueId; +import seedu.address.model.order.Order; + +/** + * Tests that a {@code Order}'s {@code UniqueId} matches the Unique ID given. + */ +public class UniqueOrderIdPredicate implements Predicate { + private final UniqueId uniqueId; + + public UniqueOrderIdPredicate(UniqueId uniqueId) { + this.uniqueId = uniqueId; + } + + @Override + public boolean test(T order) { + String orderId = uniqueId.getIdToString(); + boolean isOrder = order.getId().getIdToString().equals(orderId); + return isOrder; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueOrderIdPredicate // instanceof handles nulls + && uniqueId.equals(((UniqueOrderIdPredicate) other).uniqueId)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 60472ca22a0..ba1e46c39f2 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -12,8 +12,7 @@ public class Address { public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. + The first character of the address must not be a whitespace, otherwise " " (a blank string) becomes a valid input. */ public static final String VALIDATION_REGEX = "[^\\s].*"; @@ -31,12 +30,20 @@ public Address(String address) { } /** - * Returns true if a given string is a valid email. + * Returns true if a given string is a valid address. */ public static boolean isValidAddress(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Compares an address with another address. + * @param address The other address being compared. + */ + public int compareTo(Address address) { + return this.value.compareTo(address.value); + } + @Override public String toString() { return value; diff --git a/src/main/java/seedu/address/model/person/Buyer.java b/src/main/java/seedu/address/model/person/Buyer.java new file mode 100644 index 00000000000..39677ec5420 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Buyer.java @@ -0,0 +1,118 @@ +package seedu.address.model.person; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.index.UniqueId; +import seedu.address.model.order.Order; + +/** + * Represents a buyer that is also a person. + */ +public class Buyer extends Person { + + private final List orders = new ArrayList<>(); + + /** + * Constructs a Buyer object. + * + * @param name The name of this person. + * @param phone The phone number in string. + * @param email The email, which will be checked against regex. + * @param address The address of this person, which will be checked against the regex. + * @param location The location (country) of this person. + * @param orders The orders that this buyer requests. + */ + public Buyer(Name name, + Phone phone, + Email email, + Address address, + Location location, + Collection orders) { + super(PersonCategory.BUYER, name, phone, email, address, location); + if (orders != null) { + this.orders.addAll(orders); + } + } + + /** + * Constructs a Buyer object. + * By default, it should be PersonCategory.Buyer + * + * @param name The name of this person. + * @param phone The phone number in string. + * @param email The email, which will be checked against regex. + * @param address The address of this person, which will be checked against the regex. + * @param orders The orders that this buyer requests. + */ + public Buyer(Name name, + Phone phone, + Email email, + Address address, + Collection orders) { + super(PersonCategory.BUYER, name, phone, email, address); + if (orders != null) { + this.orders.addAll(orders); + } + } + + public List getOrderIds() { + return orders; + } + + /** + * Adds all orders in a Collection. + * + * @param orders The collection of orders to be added. + */ + public void addOrders(Collection orders) { + if (orders != null) { + this.orders.addAll(orders); + } + } + + /** + * Deletes a specific order from the list of orders. + * + * @param order The order to be deleted. + */ + public void deleteOrder(Order order) { + UniqueId orderId = order.getId(); + orders.remove(orderId); + } + + /** + * Compares a buyer with another buyer in default way in terms of the number of orders that they have. + * @param buyer The other buyer being compared. + * @return The method returns 0 if the buyer and the other buyer has the same number of orders. + * A value less than 0 is returned if the buyer has less order than the other buyer, + * and a value greater than 0 if the buyer has more order than the other buyer. + */ + public int compareTo(Buyer buyer) { + return this.orders.size() - buyer.orders.size(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof Buyer) { + Buyer otherBuyer = (Buyer) other; + return super.equals(otherBuyer) && orders.equals(otherBuyer.getOrderIds()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(getPersonCategory(), getName(), getPhone(), getEmail(), getAddress(), orders); + } + + @Override + public String toString() { + return super.toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Deliverer.java b/src/main/java/seedu/address/model/person/Deliverer.java new file mode 100644 index 00000000000..e95cd7f64a9 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Deliverer.java @@ -0,0 +1,100 @@ +package seedu.address.model.person; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.index.UniqueId; + +/** + * Represents a deliverer that is also a person. + */ +public class Deliverer extends Person { + + private final List orders = new ArrayList<>(); + + /** + * Constructs a deliverer object. + * + * @param name The name of this person. + * @param phone The phone number in string. + * @param email The email, which will be checked against regex. + * @param address The address of this person, which will be checked against the regex. + * @param location The location (country) of this person. + * @param orders The orders that this deliverer is dispatched. + */ + public Deliverer(Name name, + Phone phone, + Email email, + Address address, + Location location, + List orders) { + super(PersonCategory.DELIVERER, name, phone, email, address, location); + if (orders != null) { + this.orders.addAll(orders); + } + } + + /** + * Constructs a deliverer object. + * By default, it should be PersonCategory.Deliverer + * + * @param name The name of this person. + * @param phone The phone number in string. + * @param email The email, which will be checked against regex. + * @param address The address of this person, which will be checked against the regex. + * @param orders The orders that this deliverer is dispatched. + */ + public Deliverer(Name name, + Phone phone, + Email email, + Address address, + List orders) { + super(PersonCategory.DELIVERER, name, phone, email, address); + if (orders != null) { + this.orders.addAll(orders); + } + } + + /** + * Gets the list of unique ids of the orders. + * @return The list of unique ids. + */ + public List getOrders() { + return orders; + } + + /** + * Compares a deliverer with another deliverer in default way in terms of the number of orders that they have. + * @param deliverer The other buyer being compared. + * @return The method returns 0 if the deliverer and the other deliverer has the same number of orders. + * A value less than 0 is returned if the deliverer has less order than the other deliverer, + * and a value greater than 0 if the deliverer has more order than the other deliverer. + */ + public int compareTo(Deliverer deliverer) { + return this.orders.size() - deliverer.orders.size(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof Deliverer) { + Deliverer otherDeliverer = (Deliverer) other; + return super.equals(otherDeliverer) && orders.equals(otherDeliverer.orders); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(getName(), getPhone(), getEmail(), getAddress(), orders); + } + + @Override + public String toString() { + return super.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index f866e7133de..ac2a69ee6bc 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -51,6 +51,14 @@ public static boolean isValidEmail(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Compares an email with another email. + * @param email The other email being compared. + */ + public int compareTo(Email email) { + return this.value.compareTo(email.value); + } + @Override public String toString() { return value; diff --git a/src/main/java/seedu/address/model/person/Location.java b/src/main/java/seedu/address/model/person/Location.java new file mode 100644 index 00000000000..f9cbc5d8634 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Location.java @@ -0,0 +1,50 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Person's location in the address book. + * Guarantees: immutable. + */ +public class Location { + public static final String MESSAGE_CONSTRAINTS = + "Locations should only contain alphanumeric characters and spaces, and it should not be blank"; + + public final String location; + + /** + * Constructs a {@code Location}. + * + * @param location A valid location. + */ + public Location(String location) { + requireNonNull(location); + this.location = location; + } + + /** + * Compares a location with another location. + * @param loc The other location. + */ + public int compareTo(Location loc) { + return this.location.compareTo(loc.location); + } + + @Override + public String toString() { + return location; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Location // instanceof handles nulls + && location.equals(((Location) other).location)); // state check + } + + @Override + public int hashCode() { + return location.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/MasterList.java b/src/main/java/seedu/address/model/person/MasterList.java new file mode 100644 index 00000000000..73af098ecae --- /dev/null +++ b/src/main/java/seedu/address/model/person/MasterList.java @@ -0,0 +1,24 @@ +package seedu.address.model.person; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; + +/** + * Stores all unique/distinct Buyers, Deliverers, and Suppliers. + */ +public class MasterList { + private final ObservableList internalList = FXCollections.observableArrayList(); + + public ObservableList getMasterList() { + return internalList; + } + + public void addAll(FilteredList c) { + internalList.addAll(c); + } + + public void clear() { + internalList.clear(); + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 79244d71cf7..24eed13693a 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -12,10 +12,6 @@ public class Name { public static final String MESSAGE_CONSTRAINTS = "Names should only contain alphanumeric characters and spaces, and it should not be blank"; - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; public final String fullName; @@ -38,6 +34,13 @@ public static boolean isValidName(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Compares two name instances. + * @param name The name being compared. + */ + public int compareTo(Name name) { + return this.fullName.compareTo(name.fullName); + } @Override public String toString() { diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 8ff1d83fe89..10f71f7b7ca 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -2,12 +2,9 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import java.util.Collections; -import java.util.HashSet; import java.util.Objects; -import java.util.Set; -import seedu.address.model.tag.Tag; +import seedu.address.model.order.Order; /** * Represents a Person in the address book. @@ -15,25 +12,53 @@ */ public class Person { + private Order order; + // Identity fields private final Name name; private final Phone phone; private final Email email; + private final PersonCategory personCategory; // Data fields private final Address address; - private final Set tags = new HashSet<>(); + private final Location location; + + + /** + * Every field must be present and not null, except Location, which is + * set by default to Singapore. + */ + public Person(PersonCategory personCategory, + Name name, + Phone phone, + Email email, + Address address) { + requireAllNonNull(personCategory, name, phone, email, address); + this.personCategory = personCategory; + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.location = new Location("Singapore"); + } /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(PersonCategory personCategory, + Name name, + Phone phone, + Email email, + Address address, + Location location) { + requireAllNonNull(personCategory, name, phone, email, address); + this.personCategory = personCategory; this.name = name; this.phone = phone; this.email = email; this.address = address; - this.tags.addAll(tags); + this.location = location; } public Name getName() { @@ -52,16 +77,17 @@ public Address getAddress() { return address; } - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); + + public Location getLocation() { + return location; + } + + public PersonCategory getPersonCategory() { + return personCategory; } /** - * Returns true if both persons have the same name. + * Returns true if both persons have the same name and the same email address. * This defines a weaker notion of equality between two persons. */ public boolean isSamePerson(Person otherPerson) { @@ -70,7 +96,8 @@ public boolean isSamePerson(Person otherPerson) { } return otherPerson != null - && otherPerson.getName().equals(getName()); + && otherPerson.getName().equals(getName()) + && otherPerson.getEmail().equals(getEmail()); } /** @@ -88,17 +115,18 @@ public boolean equals(Object other) { } Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) + return otherPerson.getPersonCategory().equals(getPersonCategory()) + && otherPerson.getName().equals(getName()) && otherPerson.getPhone().equals(getPhone()) && otherPerson.getEmail().equals(getEmail()) && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); + && otherPerson.getLocation().equals(getLocation()); } @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(personCategory, name, phone, email, address); } @Override @@ -110,13 +138,9 @@ public String toString() { .append("; Email: ") .append(getEmail()) .append("; Address: ") - .append(getAddress()); - - Set tags = getTags(); - if (!tags.isEmpty()) { - builder.append("; Tags: "); - tags.forEach(builder::append); - } + .append(getAddress()) + .append("; Location: ") + .append(getLocation()); return builder.toString(); } diff --git a/src/main/java/seedu/address/model/person/PersonCategory.java b/src/main/java/seedu/address/model/person/PersonCategory.java new file mode 100644 index 00000000000..6fdfa831525 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PersonCategory.java @@ -0,0 +1,66 @@ +package seedu.address.model.person; + +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Arrays; + +/** + * Represents a person's type in the Address Book. + * There are only three types of people - Buyer, Deliverer, and Supplier. + */ +public enum PersonCategory { + BUYER("Buyer"), + DELIVERER("Deliverer"), + SUPPLIER("Supplier"); + + public static final String MESSAGE_CONSTRAINTS = + "PersonCategory should only contain alphanumeric characters, it is case sensitive and it should not " + + "be blank. It can only be one of the following types: 'Buyer', 'Deliverer' and 'Supplier'."; + + /* + * The first character of the category must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + private final String value; + + /** + * Constructs a {@code PersonCategory}. + * + * @param personCategory A valid person category. + */ + PersonCategory(String personCategory) { + value = personCategory; + } + + /** + * Converts a string representation to the actual enum value + * + * @param personCategory Either BUYER, DELIVERER, or SUPPLIER + */ + public static PersonCategory getFromString(String personCategory) { + checkArgument(isValidPersonCategory(personCategory), MESSAGE_CONSTRAINTS); + return Arrays.stream(PersonCategory.class.getEnumConstants()) + .filter(x -> x.toString().equals(personCategory)) + .findFirst().orElse(PersonCategory.BUYER); + } + + /** + * Returns true if a given string is a valid person type. + * + * @param test the string to test if valid. + * @return true if test is a valid person type, false if test is an invalid person type. + */ + public static boolean isValidPersonCategory(String test) { + boolean isValidPersonCategory = + Arrays.stream(PersonCategory.class.getEnumConstants()).map(x -> x.value).anyMatch(x -> x.equals(test)); + + return test.matches(VALIDATION_REGEX) && isValidPersonCategory; + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index 872c76b382f..973fa55e766 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -9,10 +9,11 @@ */ 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,}"; + public final String value; /** @@ -33,6 +34,14 @@ public static boolean isValidPhone(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Compares two phone instances. + * @param phone The phone being compared. + */ + public int compareTo(Phone phone) { + return this.value.compareTo(phone.value); + } + @Override public String toString() { return value; diff --git a/src/main/java/seedu/address/model/person/Supplier.java b/src/main/java/seedu/address/model/person/Supplier.java new file mode 100644 index 00000000000..ebd7c20c842 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Supplier.java @@ -0,0 +1,123 @@ +package seedu.address.model.person; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.index.UniqueId; + +/** + * Represents a supplier in the address book. + */ +public class Supplier extends Person { + + private final List pets = new ArrayList<>(); + + /** + * Constructs a supplier object. + * + * @param name The name of this person. + * @param phone The phone number in string. + * @param email The email, which will be checked against regex. + * @param address The address of this person, which will be checked against the regex. + * @param location The location (country) of this person. + * @param pets The pets supplied by this supplier. + */ + public Supplier(Name name, + Phone phone, + Email email, + Address address, + Location location, + Collection pets) { + super(PersonCategory.SUPPLIER, name, phone, email, address, location); + if (pets != null) { + this.pets.addAll(pets); + } + } + + /** + * Constructs a supplier object. + * By default, it should be PersonCategory.Supplier + * + * @param name The name of this person. + * @param phone The phone number in string. + * @param email The email, which will be checked against regex. + * @param address The address of this person, which will be checked against the regex. + * @param pets The pets supplied by this supplier. + */ + public Supplier(Name name, + Phone phone, + Email email, + Address address, + Collection pets) { + super(PersonCategory.SUPPLIER, name, phone, email, address); + if (pets != null) { + this.pets.addAll(pets); + } + } + + public List getPetIds() { + return pets; + } + + /** + * Adds all pets in a collection to the list. + * + * @param pets New pets to be added + */ + public void addPets(Collection pets) { + if (pets != null) { + this.pets.addAll(pets); + } + } + + /** + * Deletes a specific pet from the list based on index. + * + * @param index The index of the pet to be deleted + */ + public void deletePet(int index) { + pets.remove(index); + } + + /** + * Compares a supplier with another supplier in default way + * in terms of the number of pets that are on sale that they have. + * @param supplier The other buyer being compared. + * @return The method returns 0 if the supplier and the other supplier has the same number of pets that are on sale. + * A value less than 0 is returned if the supplier has lesser pets that are on sale than the other supplier, + * and a value greater than 0 if the supplier has more pets that are on sale than the other supplier. + */ + public int compareTo(Supplier supplier) { + return this.pets.size() - supplier.pets.size(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof Supplier) { + Supplier otherSupplier = (Supplier) other; + return super.equals(otherSupplier) && pets.equals(otherSupplier.getPetIds()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return Objects.hash(getPersonCategory(), + getName(), + getPhone(), + getEmail(), + getAddress(), + pets); + } + + @Override + public String toString() { + return super.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/person/UniqueBuyerList.java b/src/main/java/seedu/address/model/person/UniqueBuyerList.java new file mode 100644 index 00000000000..76e46f3df7c --- /dev/null +++ b/src/main/java/seedu/address/model/person/UniqueBuyerList.java @@ -0,0 +1,16 @@ +package seedu.address.model.person; + +/** + * A list of buyers that enforces uniqueness between its elements and does not allow nulls. + * A buyer is considered unique by comparing using {@code Buyer#isSameBuyer(Buyer)}. + * As such, adding and updating of buyers uses Buyer#isSameBuyer(Buyer) for equality to ensure that the buyer + * being added or updated is unique in terms of identity in the UniqueBuyerList. + * However, the removal of a buyer uses Buyer#equals(Object) to ensure that the buyer with exactly the same fields + * will be removed. + * + * Supports a minimal set of list operations. + * + */ +public class UniqueBuyerList extends UniquePersonList { + +} diff --git a/src/main/java/seedu/address/model/person/UniqueDelivererList.java b/src/main/java/seedu/address/model/person/UniqueDelivererList.java new file mode 100644 index 00000000000..4e53cfc9975 --- /dev/null +++ b/src/main/java/seedu/address/model/person/UniqueDelivererList.java @@ -0,0 +1,15 @@ +package seedu.address.model.person; + +/** + * A list of deliverers that enforces uniqueness between its elements and does not allow nulls. + * A deliverer is considered unique by comparing using {@code Deliverer#isSameDeliverer(Deliverer)}. + * As such, adding and updating of deliverers uses Deliverer#isSameDeliverer(Deliverer) for equality to ensure that + * the deliverer being added or updated is unique in terms of identity in the UniqueDelivererList. + * However, the removal of a deliverer uses Deliverer#equals(Object) to ensure that the deliverer with exactly the + * same fields will be removed. + * + * Supports a minimal set of list operations. + * + */ +public class UniqueDelivererList extends UniquePersonList{ +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 0fee4fe57e6..3691e4bad73 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -14,24 +15,24 @@ /** * 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. + * persons uses Person#isSamePerson(Person) for equality 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) + * 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 { +public class UniquePersonList implements Iterable { - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = + 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) { + public boolean contains(T toCheck) { requireNonNull(toCheck); return internalList.stream().anyMatch(toCheck::isSamePerson); } @@ -40,7 +41,7 @@ public boolean contains(Person toCheck) { * Adds a person to the list. * The person must not already exist in the list. */ - public void add(Person toAdd) { + public void add(T toAdd) { requireNonNull(toAdd); if (contains(toAdd)) { throw new DuplicatePersonException(); @@ -53,7 +54,7 @@ public void add(Person toAdd) { * {@code target} must exist in the list. * The person identity of {@code editedPerson} must not be the same as another existing person in the list. */ - public void setPerson(Person target, Person editedPerson) { + public void setPerson(T target, T editedPerson) { requireAllNonNull(target, editedPerson); int index = internalList.indexOf(target); @@ -72,14 +73,14 @@ public void setPerson(Person target, Person editedPerson) { * Removes the equivalent person from the list. * The person must exist in the list. */ - public void remove(Person toRemove) { + public void remove(T toRemove) { requireNonNull(toRemove); if (!internalList.remove(toRemove)) { throw new PersonNotFoundException(); } } - public void setPersons(UniquePersonList replacement) { + public void setPersons(UniquePersonList replacement) { requireNonNull(replacement); internalList.setAll(replacement.internalList); } @@ -88,7 +89,7 @@ public void setPersons(UniquePersonList replacement) { * Replaces the contents of this list with {@code persons}. * {@code persons} must not contain duplicate persons. */ - public void setPersons(List persons) { + public void setPersons(List persons) { requireAllNonNull(persons); if (!personsAreUnique(persons)) { throw new DuplicatePersonException(); @@ -97,15 +98,23 @@ public void setPersons(List persons) { internalList.setAll(persons); } + /** + * Sorts the list using the specified comparator. + * @param comparator The specified comparator. + */ + public void sort(Comparator comparator) { + internalList.sort(comparator); + } + /** * Returns the backing list as an unmodifiable {@code ObservableList}. */ - public ObservableList asUnmodifiableObservableList() { + public ObservableList asUnmodifiableObservableList() { return internalUnmodifiableList; } @Override - public Iterator iterator() { + public Iterator iterator() { return internalList.iterator(); } @@ -122,9 +131,9 @@ public int hashCode() { } /** - * Returns true if {@code persons} contains only unique persons. + * Returns true iff {@code persons} contains only unique persons. */ - private boolean personsAreUnique(List 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))) { @@ -134,4 +143,5 @@ private boolean personsAreUnique(List persons) { } return true; } + } diff --git a/src/main/java/seedu/address/model/person/UniqueSupplierList.java b/src/main/java/seedu/address/model/person/UniqueSupplierList.java new file mode 100644 index 00000000000..dbbf32e4644 --- /dev/null +++ b/src/main/java/seedu/address/model/person/UniqueSupplierList.java @@ -0,0 +1,14 @@ +package seedu.address.model.person; + +/** + * A list of suppliers that enforces uniqueness between its elements and does not allow nulls. + * A supplier is considered unique by comparing using {@code Supplier#isSameSupplier(Supplier)}. + * As such, adding and updating of suppliers uses Supplier#isSameSupplier(Supplier) for equality to ensure that the + * supplier being added or updated is unique in terms of identity in the UniqueSupplierList. + * However, the removal of a person uses Supplier#equals(Object) to ensure that the person with exactly the + * same fields will be removed. + * + * Supports a minimal set of list operations. + */ +public class UniqueSupplierList extends UniquePersonList{ +} diff --git a/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java new file mode 100644 index 00000000000..516d81c4f7d --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/AddressContainsKeywordsPredicate.java @@ -0,0 +1,32 @@ + +package seedu.address.model.person.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Address} matches any of the keywords given. + */ +public class AddressContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public AddressContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(T person) { + return keywords.stream() + .allMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getAddress().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddressContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((AddressContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java new file mode 100644 index 00000000000..0d8cf16d36f --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Email} matches any of the keywords given. + */ +public class EmailContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public EmailContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(T person) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EmailContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((EmailContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/predicates/LocationContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/LocationContainsKeywordsPredicate.java new file mode 100644 index 00000000000..510e8d9991e --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/LocationContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Location} matches any of the keywords given. + */ +public class LocationContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public LocationContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + return keywords.stream() + .allMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getLocation().location, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LocationContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((LocationContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java similarity index 61% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java index c9b5868427c..1026a94253c 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java @@ -1,14 +1,14 @@ -package seedu.address.model.person; +package seedu.address.model.person.predicates; import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; /** * Tests that a {@code Person}'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) { @@ -16,9 +16,9 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + public boolean test(T person) { + String nameInLowerCase = person.getName().fullName.toLowerCase(); + return keywords.stream().map(String::toLowerCase).allMatch(keyword -> nameInLowerCase.contains(keyword)); } @Override diff --git a/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java new file mode 100644 index 00000000000..ea75e9ca0a4 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.person.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class PhoneContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public PhoneContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(T person) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getPhone().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PhoneContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((PhoneContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/pet/Age.java b/src/main/java/seedu/address/model/pet/Age.java new file mode 100644 index 00000000000..5daead741ba --- /dev/null +++ b/src/main/java/seedu/address/model/pet/Age.java @@ -0,0 +1,71 @@ +package seedu.address.model.pet; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDate; +import java.time.Period; + +/** + * Represents the age of a pet. Wraps a single integer. + * If the age does not match the date of birth, the age will change accordingly. + */ +public class Age { + + public static final String MESSAGE_USAGE = + "Age should be a non-negative integer, such as 0, 5, and 10"; + private final int value; + + /** + * Constructs the Age object. + * @param value The integer to be wrapped. + */ + public Age(int value) { + this.value = value; + } + + /** + * Calculates the age based on date of birth. + * + * @param pet The pet that needs to be updated the age. + * @return Age in integer + */ + public static Age generateAge(Pet pet) { + requireNonNull(pet); + LocalDate currentDate = LocalDate.now(); + LocalDate dateOfBirth = pet.getDateOfBirth().getDate(); + requireAllNonNull(currentDate, dateOfBirth); + int result = Period.between(dateOfBirth, currentDate).getYears(); + return new Age(result); + } + + /** + * Gets the age. + * @return Age in integer + */ + public int getValue() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof Age) { + Age otherAge = (Age) other; + return value == otherAge.value; + } else { + return false; + } + } + + @Override + public int hashCode() { + return Integer.hashCode(value); + } + + @Override + public String toString() { + return "Age: " + Integer.toString(value); + } +} diff --git a/src/main/java/seedu/address/model/pet/Color.java b/src/main/java/seedu/address/model/pet/Color.java new file mode 100644 index 00000000000..0175e000e0b --- /dev/null +++ b/src/main/java/seedu/address/model/pet/Color.java @@ -0,0 +1,51 @@ +package seedu.address.model.pet; + +/** + * Represents the color of a pet. + */ +public class Color implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = + "Colors should only contain alphanumeric characters and spaces, and it should not be blank"; + private final String value; + + /** + * Constructs a color object. + * + * @param value The string representation of a color. + */ + public Color(String value) { + this.value = (value == null) || !value.matches("^[a-zA-Z0-9 ]*$*") ? "" : value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Color // instanceof handles nulls + && value.equals(((Color) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return "Color: " + value; + } + + /** + * Gets the color of a pet. + * + * @return The color. + */ + public String getValue() { + return value; + } + + @Override + public int compareTo(Color o) { + return this.value.compareTo(o.getValue()); + } +} diff --git a/src/main/java/seedu/address/model/pet/ColorPattern.java b/src/main/java/seedu/address/model/pet/ColorPattern.java new file mode 100644 index 00000000000..10295b7f726 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/ColorPattern.java @@ -0,0 +1,60 @@ +package seedu.address.model.pet; + +/** + * Represents the color pattern of a pet. For example, striped, grid, dark. + */ +public class ColorPattern implements Comparable { + + public static final String MESSAGE_CONSTRAINTS = + "Color patterns should only contain alphanumeric characters and spaces, and it should not be blank"; + private final String value; + + /** + * Constructs a ColorPattern object. + * + * @param value The string representation of color pattern. + */ + public ColorPattern(String value) { + if (value == null || !value.matches("^[a-zA-Z0-9\\s]+$")) { + this.value = ""; + } else { + this.value = value; + } + } + + /** + * Gets the color pattern of a pet. + * + * @return The color pattern. + */ + public String getValue() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof ColorPattern) { + ColorPattern otherColorPattern = (ColorPattern) other; + return value.equals(otherColorPattern.getValue()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return "Color pattern: " + value; + } + + @Override + public int compareTo(ColorPattern o) { + return this.value.compareTo(o.value); + } +} diff --git a/src/main/java/seedu/address/model/pet/DateOfBirth.java b/src/main/java/seedu/address/model/pet/DateOfBirth.java new file mode 100644 index 00000000000..51c6d99ab18 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/DateOfBirth.java @@ -0,0 +1,79 @@ +package seedu.address.model.pet; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.ModelManager; + +/** + * Represents the date of birth of a pet. + */ +public class DateOfBirth implements Comparable { + + public static final String MESSAGE_USAGE = "The date of birth should be in this format preferably: " + + ModelManager.PREFERRED_DATE_FORMAT + + "\nYou cannot enter a future date for date of birth"; + private static final String[] ACCEPTABLE_DATE_FORMATS = ModelManager.ACCEPTABLE_DATE_FORMATS; + + private static final String PREFERRED_DATE_FORMAT = ModelManager.PREFERRED_DATE_FORMAT; + + private static final DateTimeFormatter PREFERRED_FORMATTER = ModelManager.PREFERRED_FORMATTER; + + private final LocalDate date; + + public DateOfBirth(LocalDate date) { + this.date = date; + } + + /** + * Parses the date from a string representation. + * + * @param toParse The string to be parsed. + * @return The DateOfBirth object. + * @throws IllegalValueException If the string cannot be parsed by any acceptable date format + */ + public static DateOfBirth parseString(String toParse) throws IllegalValueException { + LocalDate output; + for (String format: ACCEPTABLE_DATE_FORMATS) { + try { + output = LocalDate.parse(toParse, DateTimeFormatter.ofPattern(format)); + return new DateOfBirth(output); + } catch (DateTimeParseException exception) { + //Do nothing because it will eventually throw an exception if no formats match + } + } + throw new IllegalValueException(MESSAGE_USAGE); + } + + @Override + public String toString() { + return "Date of birth: " + date.format(PREFERRED_FORMATTER); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DateOfBirth // instanceof handles nulls + && date.equals(((DateOfBirth) other).date)); // state check + } + + @Override + public int hashCode() { + return date.hashCode(); + } + + public LocalDate getDate() { + return date; + } + + public String getPreferredDateInString() { + return date.format(PREFERRED_FORMATTER); + } + + @Override + public int compareTo(DateOfBirth o) { + return this.date.compareTo(o.date); + } +} diff --git a/src/main/java/seedu/address/model/pet/Height.java b/src/main/java/seedu/address/model/pet/Height.java new file mode 100644 index 00000000000..d4c98c3cee7 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/Height.java @@ -0,0 +1,51 @@ +package seedu.address.model.pet; + +/** + * Represents the height of a pet. + */ +public class Height implements Comparable { + + public static final String UNIT = "cm"; + public static final String MESSAGE_USAGE = + "The height should be in " + UNIT + " and be a non-negative decimal number, such as 22.8"; + + private final double value; + + /** + * Constructs the height object. + * @param value The height in double floating point number. + */ + public Height(double value) { + if (value < 0) { + this.value = 0; + } else { + this.value = value; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Height // instanceof handles nulls + && value == ((Height) other).value); // state check + } + + @Override + public int hashCode() { + return Double.hashCode(value); + } + + @Override + public String toString() { + return "Height: " + value + " " + UNIT; + } + + public double getValue() { + return value; + } + + @Override + public int compareTo(Height o) { + return Double.compare(this.value, o.value); + } +} diff --git a/src/main/java/seedu/address/model/pet/Pet.java b/src/main/java/seedu/address/model/pet/Pet.java new file mode 100644 index 00000000000..269e85b9c1b --- /dev/null +++ b/src/main/java/seedu/address/model/pet/Pet.java @@ -0,0 +1,338 @@ +package seedu.address.model.pet; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Calendar; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import seedu.address.commons.core.index.UniqueId; +import seedu.address.commons.core.index.UniqueIdGenerator; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.order.Price; +import seedu.address.model.person.Supplier; + +/** + * Represents a pet. + */ +public class Pet { + + private static final UniqueIdGenerator PET_ID_GENERATOR = new UniqueIdGenerator(); + + private final UniqueId id; + private final PetName name; + private Supplier supplier; + private final Color color; + private final ColorPattern colorPattern; + private final DateOfBirth dateOfBirth; + private final Species species; + private final Weight weight; + private final Height height; + private final VaccinationStatus vaccinationStatus; + private final Price price; + private final Set certificates = new HashSet<>(); + + /** + * Constructs a pet completely. + * + * @param id The unique id of this pet. + * @param name The name of this pet. + * @param supplier The owner of this pet. Could be a Buyer or a Supplier. + * @param color The color of this pet. Could be red. + * @param colorPattern The color pattern. Could be stripped. + * @param dateOfBirth The date of birth. + * @param species Its species, for example, chihuahua. + * @param weight Its weight. + * @param height Its height (or length if it walks on fours). + * @param vaccinationStatus Its vaccination status (vaccinated or not). + * @param price Its price for sale. + * @param certificates Its certificates, for example, noble blood. + */ + public Pet(UniqueId id, + PetName name, + Supplier supplier, + Color color, + ColorPattern colorPattern, + DateOfBirth dateOfBirth, + Species species, + Weight weight, + Height height, + VaccinationStatus vaccinationStatus, + Price price, + Set certificates) { + requireAllNonNull(name, color, colorPattern, dateOfBirth, species, weight, height, vaccinationStatus, price); + UniqueIdGenerator.addToStoredIdPet(id); + this.id = id; + this.name = name; + this.supplier = supplier; + this.species = species; + this.color = color; + this.colorPattern = colorPattern; + this.dateOfBirth = dateOfBirth; + this.weight = weight; + this.height = height; + this.vaccinationStatus = vaccinationStatus; + this.price = price; + this.certificates.addAll(certificates); + } + + /** + * Constructs a pet completely. + * + * @param name The name of this pet. + * @param supplier The owner of this pet. Could be a Buyer or a Supplier. + * @param color The color of this pet. Could be red. + * @param colorPattern The color pattern. Could be stripped. + * @param dateOfBirth The date of birth. + * @param species Its species, for example, chihuahua. + * @param weight Its weight. + * @param height Its height (or length if it walks on fours). + * @param vaccinationStatus Its vaccination status (vaccinated or not). + * @param price Its price for sale. + * @param certificates Its certificates, for example, noble blood. + */ + public Pet(PetName name, + Supplier supplier, + Color color, + ColorPattern colorPattern, + DateOfBirth dateOfBirth, + Species species, + Weight weight, + Height height, + VaccinationStatus vaccinationStatus, + Price price, + Set certificates) { + requireAllNonNull(name, color, colorPattern, dateOfBirth, species, weight, height, vaccinationStatus, price); + UniqueId currId = PET_ID_GENERATOR.next(); + while (UniqueIdGenerator.storedIdPetContains(currId)) { + currId = PET_ID_GENERATOR.next(); + } + this.id = currId; + this.name = name; + this.supplier = supplier; + this.species = species; + this.color = color; + this.colorPattern = colorPattern; + this.dateOfBirth = dateOfBirth; + this.weight = weight; + this.height = height; + this.vaccinationStatus = vaccinationStatus; + this.price = price; + if (certificates != null) { + this.certificates.addAll(certificates); + } + } + + /** + * Constructs a pet completely. + * + * @param name The name of this pet. + * @param color The color of this pet. Could be red. + * @param colorPattern The color pattern. Could be stripped. + * @param dateOfBirthString The date of birth in string. + * @param species Its species, for example, chihuahua. + * @param weight Its weight. + * @param height Its height (or length if it walks on fours). + * @param price Its price for sale. + * @param certificates Its certificates, for example, noble blood. + */ + public Pet(PetName name, + Color color, + ColorPattern colorPattern, + String dateOfBirthString, + Species species, + Weight weight, + Height height, + Price price, + Set certificates) throws IllegalValueException { + this(name, + null, + color, + colorPattern, + DateOfBirth.parseString(dateOfBirthString), + species, + weight, + height, + VaccinationStatus.defaultStatus(), + price, + certificates); + + } + + /** + * Gets the age of a pet. + * @return The age + */ + public int getAge() { + int currYear = Calendar.getInstance().get(Calendar.YEAR); + int bornYear = this.dateOfBirth.getDate().getYear(); + return currYear - bornYear; + } + + /** + * Compares a pet with another pet in default way in terms of the age. + * @param pet The other pet being compared. + * @return The method returns 0 if the pet and the other pet has the same age. + * A value less than 0 is returned if the pet is younger than the other pet, + * and a value greater than 0 if the pet is older than the other pet. + */ + public int compareTo(Pet pet) { + return this.getAge() - pet.getAge(); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName().toString()) + .append("; ") + .append(getSpecies().toString()) + .append("; ") + .append(getDateOfBirth().toString()) + .append("; ") + .append(getWeight().toString()) + .append("; ") + .append(getHeight().toString()) + .append("; ") + .append(getColor().toString()) + .append("; ") + .append(getColorPattern().toString()) + .append("; ") + .append(getPrice().toString()) + .append("; ") + .append(getVaccinationStatus().toString()); + + Set certificates = getCertificates(); + if (!certificates.isEmpty()) { + builder.append("; Certificates: "); + certificates.forEach(builder::append); + } + return builder.toString(); + } + + /** + * Returns true if both pets have the same identity and data fields. + * This defines a stronger notion of equality between two pets. + * + * @return true iff they have exactly the same fields. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Pet)) { + return false; + } + + return id.equals(((Pet) other).id); + } + + /** + * Returns a combination of hash codes of all instance objects. + * Use this method for custom fields hashing instead of implementing your own + * + * @return a hash code representing this pet. + */ + @Override + public int hashCode() { + return Objects.hash(id); + } + + /** + * Returns true if both sets have the same name. + * This defines a weaker notion of equality between two pets. + * + * @return true iff they have the same name. + */ + public boolean isSamePet(Pet otherPet) { + if (otherPet == this) { + return true; + } + + return otherPet != null + && otherPet.getName().equals(getName()) + && otherPet.getPrice().equals(getPrice()) + && otherPet.getCertificates().equals(getCertificates()) + && otherPet.getColor().equals(getColor()) + && otherPet.getDateOfBirth().equals(getDateOfBirth()) + && otherPet.getSpecies().equals(getSpecies()) + && otherPet.getColorPattern().equals(getColorPattern()) + && otherPet.getVaccinationStatus().equals(getVaccinationStatus()) + && otherPet.getWeight().equals(getWeight()) + && otherPet.getHeight().equals(getHeight()); + } + + public PetName getName() { + return name; + } + + public Color getColor() { + return color; + } + + public ColorPattern getColorPattern() { + return colorPattern; + } + + public DateOfBirth getDateOfBirth() { + return dateOfBirth; + } + + public Height getHeight() { + return height; + } + + public Supplier getSupplier() { + return supplier; + } + + public Species getSpecies() { + return species; + } + + public Weight getWeight() { + return weight; + } + + public UniqueId getId() { + return id; + } + + public Price getPrice() { + return price; + } + + public VaccinationStatus getVaccinationStatus() { + return vaccinationStatus; + } + + /** + * Returns an immutable pet certificate set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getCertificates() { + return Collections.unmodifiableSet(certificates); + } + + public void setSupplier(Supplier supplier) { + this.supplier = supplier; + } + + /** + * Returns true if the two uniqueId matches. + */ + public boolean hasId(UniqueId id) { + return this.id.equals(id); + } + + /** + * Compares two pets based on the number of certificates. + */ + public int compareCertificate(Pet pet) { + return Integer.compare(this.certificates.size(), pet.certificates.size()); + } + +} diff --git a/src/main/java/seedu/address/model/pet/PetCertificate.java b/src/main/java/seedu/address/model/pet/PetCertificate.java new file mode 100644 index 00000000000..19bc4e39f18 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/PetCertificate.java @@ -0,0 +1,44 @@ +package seedu.address.model.pet; + +/** + * Represents the certificates that a pet has. For example, purebred certificate. + */ +public class PetCertificate { + public static final String MESSAGE_CONSTRAINTS = + "Pet certificates should only contain alphanumeric characters and spaces, and it should not be blank"; + private final String certificate; + + /** + * Constructs a PetCertificate object. + * + * @param certificate The string representation of the certificate. + */ + public PetCertificate(String certificate) { + if (certificate == null) { + this.certificate = ""; + } else { + this.certificate = certificate; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PetCertificate // instanceof handles nulls + && certificate.equals(((PetCertificate) other).certificate)); // state check + } + + @Override + public int hashCode() { + return certificate.hashCode(); + } + + @Override + public String toString() { + return " [ " + certificate + " ] "; + } + + public String getCertificate() { + return certificate; + } +} diff --git a/src/main/java/seedu/address/model/pet/PetGrader.java b/src/main/java/seedu/address/model/pet/PetGrader.java new file mode 100644 index 00000000000..976a834625a --- /dev/null +++ b/src/main/java/seedu/address/model/pet/PetGrader.java @@ -0,0 +1,122 @@ +package seedu.address.model.pet; + +import static seedu.address.model.order.PriceRange.LOWER_THAN_RANGE; +import static seedu.address.model.order.PriceRange.WITHIN_RANGE; + +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.order.Order; +import seedu.address.model.order.PriceRange; + +/** + * Grades how fit a pet is given an order. + */ +public class PetGrader { + + private static final double DEFAULT_AGE_SCORE_WEIGHT = 30; + private static final double DEFAULT_COLOR_SCORE_WEIGHT = 100; + private static final double DEFAULT_COLOR_PATTERN_SCORE_WEIGHT = 100; + private static final double DEFAULT_SPECIES_SCORE_WEIGHT = 500; + private static final double DEFAULT_PRICE_SCORE_WEIGHT = 5; + + private static final Logger LOGGER = LogsCenter.getLogger(PetGrader.class); + private final Order order; + private final double ageScoreWeight; + private final double colorScoreWeight; + private final double colorPatternScoreWeight; + private final double speciesScoreWeight; + private final double priceScoreWeight; + + /** + * Constructs a PetGrader object. + * + * @param order The order to be compared with. + */ + public PetGrader(Order order) { + this.order = order; + ageScoreWeight = DEFAULT_AGE_SCORE_WEIGHT; + colorScoreWeight = DEFAULT_COLOR_SCORE_WEIGHT; + colorPatternScoreWeight = DEFAULT_COLOR_PATTERN_SCORE_WEIGHT; + speciesScoreWeight = DEFAULT_SPECIES_SCORE_WEIGHT; + priceScoreWeight = DEFAULT_PRICE_SCORE_WEIGHT; + } + + /** + * Constructs a PetGrader object. + * + * @param order The order to be compared with. + * @param ageScoreWeight The score weight for age. + * @param colorScoreWeight The score weight for color. + * @param colorPatternScoreWeight The score weight for color pattern. + * @param speciesScoreWeight The score weight for species. + * @param priceScoreWeight The score weight for price. + */ + public PetGrader(Order order, + double ageScoreWeight, + double colorScoreWeight, + double colorPatternScoreWeight, + double speciesScoreWeight, + double priceScoreWeight) { + this.order = order; + this.ageScoreWeight = ageScoreWeight; + this.colorScoreWeight = colorScoreWeight; + this.colorPatternScoreWeight = colorPatternScoreWeight; + this.speciesScoreWeight = speciesScoreWeight; + this.priceScoreWeight = priceScoreWeight; + } + + /** + * Evaluates the score of a pet. + * + * @param pet The @code{Pet} object to be evaluated. + */ + public double evaluate(Pet pet) { + double ageScore = ageScoreWeight + - ageScoreWeight * Math.abs(order.getRequest().getRequestedAge().getValue() - pet.getAge()); + double colorScore = (order.getRequest().getRequestedColor().equals(pet.getColor()) ? 1 : 0) + * colorScoreWeight; + double colorPatternScore = (order.getRequest().getRequestedColorPattern().equals(pet.getColorPattern()) ? 1 : 0) + * colorPatternScoreWeight; + double speciesScore = (order.getRequest().getRequestedSpecies().equals(pet.getSpecies()) ? 1 : 0) + * speciesScoreWeight; + + double priceScore; + PriceRange priceRange = order.getRequestedPriceRange(); + int priceRangeSituation = priceRange.comparePrice(pet.getPrice()); + + if (priceRangeSituation == WITHIN_RANGE) { + priceScore = priceScoreWeight; + } else { + priceScore = priceScoreWeight + - Math.abs((priceRangeSituation == LOWER_THAN_RANGE + ? priceRange.getLowerBound().getPrice() + : priceRange.getUpperBound().getPrice()) + - pet.getPrice().getPrice()); + } + + double sum = ageScore + colorScore + colorPatternScore + speciesScore + priceScore; + LOGGER.fine(pet.getName() + "'s score is " + sum); + return sum; + } + + public double getAgeScoreWeight() { + return ageScoreWeight; + } + + public double getColorScoreWeight() { + return colorScoreWeight; + } + + public double getColorPatternScoreWeight() { + return colorPatternScoreWeight; + } + + public double getPriceScoreWeight() { + return priceScoreWeight; + } + + public double getSpeciesScoreWeight() { + return speciesScoreWeight; + } +} diff --git a/src/main/java/seedu/address/model/pet/PetName.java b/src/main/java/seedu/address/model/pet/PetName.java new file mode 100644 index 00000000000..8b4f112b280 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/PetName.java @@ -0,0 +1,66 @@ +package seedu.address.model.pet; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Pet's name in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} + */ +public class PetName { + + public static final String MESSAGE_CONSTRAINTS = + "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String fullName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public PetName(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); + } + + /** + * Compares two name instances. + * @param name The name being compared. + */ + public int compareTo(PetName name) { + return this.fullName.compareTo(name.fullName); + } + + @Override + public String toString() { + return fullName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PetName // instanceof handles nulls + && fullName.equals(((PetName) other).fullName)); // state check + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/pet/Species.java b/src/main/java/seedu/address/model/pet/Species.java new file mode 100644 index 00000000000..49daff3b042 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/Species.java @@ -0,0 +1,49 @@ +package seedu.address.model.pet; + +/** + * Represents the species (kind) of a pet. + */ +public class Species implements Comparable { + public static final String MESSAGE_CONSTRAINTS = + "Species should only contain alphanumeric characters and spaces, and it should not be blank"; + private final String value; + + /** + * Constructs the Species object. + * + * @param value The string representation of the species. + */ + public Species(String value) { + if (value == null || !value.matches("^[a-zA-Z0-9\\s]+$")) { + this.value = ""; + } else { + this.value = value; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Species // instanceof handles nulls + && value.equals(((Species) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return "Species: " + value; + } + + public String getValue() { + return value; + } + + @Override + public int compareTo(Species o) { + return this.value.compareTo(o.value); + } +} diff --git a/src/main/java/seedu/address/model/pet/UniquePetList.java b/src/main/java/seedu/address/model/pet/UniquePetList.java new file mode 100644 index 00000000000..4be67384f03 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/UniquePetList.java @@ -0,0 +1,158 @@ +package seedu.address.model.pet; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.core.index.UniqueId; +import seedu.address.model.pet.exceptions.DuplicatePetException; +import seedu.address.model.pet.exceptions.PetNotFoundException; + +/** + * A list of pets that enforces uniqueness between its elements and does not allow nulls. + * A pet is considered unique by comparing using {@code Pet#isSamePet(Pet)}. As such, adding and updating of + * pets uses Pet#isSamePet(Pet) for equality to ensure that the pet being added or updated is + * unique in terms of identity in the UniquePetList. However, the removal of a pet uses Pet#equals(Object) to ensure + * that the pet with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Pet#isSamePet(Pet) + */ +public class UniquePetList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent pet as the given argument. + */ + public boolean contains(Pet toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSamePet); + } + + /** + * Adds a pet to the list. + * The pet must not already exist in the list. + */ + public void add(Pet toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePetException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the pet {@code target} in the list with {@code editedPet}. + * {@code target} must exist in the list. + * The pet identity of {@code editedPet} must not be the same as another existing pet in the list. + */ + public void setPet(Pet target, Pet editedPet) { + requireAllNonNull(target, editedPet); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PetNotFoundException(); + } + + if (!target.isSamePet(editedPet) && contains(editedPet)) { + throw new DuplicatePetException(); + } + + internalList.set(index, editedPet); + } + + /** + * Removes the equivalent pet from the list. + * The pet must exist in the list. + */ + public void remove(Pet toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new PetNotFoundException(); + } + } + + public void setPets(UniquePetList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code pets}. + * {@code pets} must not contain duplicate pets. + */ + public void setPets(List pets) { + requireAllNonNull(pets); + if (!petsAreUnique(pets)) { + throw new DuplicatePetException(); + } + + internalList.setAll(pets); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + /** + * Gets the list of pets from list of ids. + */ + public List getPetsFromId(List ids) { + return internalList.stream() + .filter(pet -> ids.stream() + .anyMatch(pet::hasId)) + .collect(Collectors.toList()); + } + + /** + * Sorts the list using the specified comparator. + * @param comparator The specified comparator. + */ + public void sort(Comparator comparator) { + internalList.sort(comparator); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniquePetList // instanceof handles nulls + && internalList.equals(((UniquePetList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true iff {@code pets} contains only unique pets. + */ + private boolean petsAreUnique(List pets) { + for (int i = 0; i < pets.size() - 1; i++) { + for (int j = i + 1; j < pets.size(); j++) { + if (pets.get(i).isSamePet(pets.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/pet/VaccinationStatus.java b/src/main/java/seedu/address/model/pet/VaccinationStatus.java new file mode 100644 index 00000000000..3284af34e34 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/VaccinationStatus.java @@ -0,0 +1,58 @@ +package seedu.address.model.pet; + +/** + * Represents the vaccination status of a pet. Could be either true of false. + */ +public class VaccinationStatus implements Comparable { + + public static final String MESSAGE_USAGE = "The vaccination status should be either 'true' or 'false'"; + private final boolean isVaccinated; + + /** + * Constructs the VaccinationStatus object. + * @param isVaccinated A boolean value that indicates whether the pet is vaccinated. + */ + public VaccinationStatus(boolean isVaccinated) { + this.isVaccinated = isVaccinated; + } + + public static VaccinationStatus defaultStatus() { + return new VaccinationStatus(false); + } + + public boolean getVaccinationStatus() { + return isVaccinated; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof VaccinationStatus) { + VaccinationStatus otherStatus = (VaccinationStatus) other; + return isVaccinated == otherStatus.getVaccinationStatus(); + } else { + return false; + } + } + @Override + public String toString() { + return "Vaccination status: " + (isVaccinated ? "Vaccinated" : "Not Vaccinated"); + } + + @Override + public int hashCode() { + return Boolean.hashCode(isVaccinated); + } + + @Override + public int compareTo(VaccinationStatus o) { + if (this.isVaccinated && o.isVaccinated || (!this.isVaccinated && !o.isVaccinated)) { + return 0; + } else if (this.isVaccinated) { + return 1; + } else { + return -1; + } + } +} diff --git a/src/main/java/seedu/address/model/pet/Weight.java b/src/main/java/seedu/address/model/pet/Weight.java new file mode 100644 index 00000000000..2d123767bf2 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/Weight.java @@ -0,0 +1,51 @@ +package seedu.address.model.pet; + +/** + * Represents the weight of a pet. + */ +public class Weight implements Comparable { + + public static final String UNIT = "kg"; + public static final String MESSAGE_USAGE = + "The weight should be in " + UNIT + " and be a non-negative decimal number, such as 23.8"; + + private final double value; + + /** + * Constructs the weight object. + * @param value The weight in double. + */ + public Weight(double value) { + if (value < 0) { + this.value = 0; + } else { + this.value = value; + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Weight // instanceof handles nulls + && value == ((Weight) other).value); // state check + } + + @Override + public int hashCode() { + return Double.hashCode(value); + } + + @Override + public String toString() { + return "Weight: " + value + " " + UNIT; + } + + public double getValue() { + return value; + } + + @Override + public int compareTo(Weight o) { + return Double.compare(this.value, o.value); + } +} diff --git a/src/main/java/seedu/address/model/pet/exceptions/DuplicatePetException.java b/src/main/java/seedu/address/model/pet/exceptions/DuplicatePetException.java new file mode 100644 index 00000000000..dd872c7d059 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/exceptions/DuplicatePetException.java @@ -0,0 +1,11 @@ +package seedu.address.model.pet.exceptions; + +/** + * Signals that the operation will result in duplicate Pets (Pets are considered duplicates if they have the same + * identity). + */ +public class DuplicatePetException extends RuntimeException { + public DuplicatePetException() { + super("Operation would result in duplicate pets"); + } +} diff --git a/src/main/java/seedu/address/model/pet/exceptions/PetNotFoundException.java b/src/main/java/seedu/address/model/pet/exceptions/PetNotFoundException.java new file mode 100644 index 00000000000..27e0a75ca8d --- /dev/null +++ b/src/main/java/seedu/address/model/pet/exceptions/PetNotFoundException.java @@ -0,0 +1,8 @@ +package seedu.address.model.pet.exceptions; + +/** + * Signals that the operation is unable to find the specified pet. + */ +public class PetNotFoundException extends RuntimeException { + +} diff --git a/src/main/java/seedu/address/model/pet/predicates/ColorContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/pet/predicates/ColorContainsKeywordsPredicate.java new file mode 100644 index 00000000000..52cad9387b1 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/predicates/ColorContainsKeywordsPredicate.java @@ -0,0 +1,34 @@ +package seedu.address.model.pet.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.pet.Pet; + +/** + * Tests that a {@code Pet}'s {@code Color} matches any of the keywords given. + */ +public class ColorContainsKeywordsPredicate implements Predicate { + private final List keywords; + + /** + * Creates a {@code ColorContainsKeywordsPredicate} based on keywords given. + */ + public ColorContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(T pet) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(pet.getColor().getValue(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ColorContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((ColorContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/pet/predicates/PetNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/pet/predicates/PetNameContainsKeywordsPredicate.java new file mode 100644 index 00000000000..397ad32d943 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/predicates/PetNameContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.pet.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.pet.Pet; + +/** + * Tests that a {@code Pet}'s {@code Name} matches any of the keywords given. + */ +public class PetNameContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public PetNameContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(T pet) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(pet.getName().fullName, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PetNameContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((PetNameContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/pet/predicates/PriceContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/pet/predicates/PriceContainsKeywordsPredicate.java new file mode 100644 index 00000000000..9ec8c585d43 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/predicates/PriceContainsKeywordsPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model.pet.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.model.pet.Pet; + +/** + * Tests that a {@code Price}'s {@code Price} matches any of the keywords given. + */ +public class PriceContainsKeywordsPredicate implements Predicate { + private final List keywords; + + /** + * Creates a {@code PriceContainsKeywordsPredicate} based on keywords given. + */ + public PriceContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(T pet) { + return keywords.stream() + .anyMatch(keyword -> keyword.equals(pet.getPrice().getPrice())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PriceContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((PriceContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/pet/predicates/SpeciesContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/pet/predicates/SpeciesContainsKeywordsPredicate.java new file mode 100644 index 00000000000..cec86d16af8 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/predicates/SpeciesContainsKeywordsPredicate.java @@ -0,0 +1,34 @@ +package seedu.address.model.pet.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.pet.Pet; + +/** + * Tests that a {@code Pet}'s {@code Species} matches any of the keywords given. + */ +public class SpeciesContainsKeywordsPredicate implements Predicate { + private final List keywords; + + /** + * Creates a {@code SpeciesContainsKeywordsPredicate} based on keywords given. + */ + public SpeciesContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(T pet) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(pet.getSpecies().getValue(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SpeciesContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((SpeciesContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/pet/predicates/UniquePetIdPredicate.java b/src/main/java/seedu/address/model/pet/predicates/UniquePetIdPredicate.java new file mode 100644 index 00000000000..665afa60c30 --- /dev/null +++ b/src/main/java/seedu/address/model/pet/predicates/UniquePetIdPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.pet.predicates; + +import java.util.function.Predicate; + +import seedu.address.commons.core.index.UniqueId; +import seedu.address.model.pet.Pet; + +/** + * Tests that a {@code Pet}'s {@code UniqueId} matches the Unique Id given. + */ +public class UniquePetIdPredicate implements Predicate { + private final UniqueId uniqueId; + + public UniquePetIdPredicate(UniqueId uniqueId) { + this.uniqueId = uniqueId; + } + + @Override + public boolean test(T pet) { + String petId = uniqueId.getIdToString(); + boolean isPet = pet.getId().getIdToString().equals(petId); + return isPet; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniquePetIdPredicate // instanceof handles nulls + && uniqueId.equals(((UniquePetIdPredicate) other).uniqueId)); // state check + } +} diff --git a/src/main/java/seedu/address/model/pet/predicates/VaccinationStatusPredicate.java b/src/main/java/seedu/address/model/pet/predicates/VaccinationStatusPredicate.java new file mode 100644 index 00000000000..7333c7fe04b --- /dev/null +++ b/src/main/java/seedu/address/model/pet/predicates/VaccinationStatusPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.pet.predicates; + +import java.util.function.Predicate; + +import seedu.address.model.pet.Pet; + +/** + * Tests that a {@code Pet}'s {@code VaccinationStatus} matches any of the keywords given. + */ +public class VaccinationStatusPredicate implements Predicate { + private final boolean status; + + /** + * Creates a {@code VaccinationStatusPredicate} based on status given. + */ + public VaccinationStatusPredicate(boolean status) { + this.status = status; + } + + @Override + public boolean test(T pet) { + return pet.getVaccinationStatus().getVaccinationStatus() == status; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof VaccinationStatusPredicate // instanceof handles nulls + && ((VaccinationStatusPredicate) other).status == status); // state check + } +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index b0ea7e7dad7..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,54 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; - - public final String tagName; - - /** - * Constructs a {@code Tag}. - * - * @param tagName A valid tag name. - */ - public Tag(String tagName) { - requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Tag // instanceof handles nulls - && tagName.equals(((Tag) other).tagName)); // state check - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - -} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..b86efad108a 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,60 +1,286 @@ package seedu.address.model.util; +import java.time.LocalDate; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import seedu.address.commons.core.index.UniqueId; +import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.order.AdditionalRequests; +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderStatus; +import seedu.address.model.order.Price; +import seedu.address.model.order.PriceRange; +import seedu.address.model.order.Request; import seedu.address.model.person.Address; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; import seedu.address.model.person.Email; +import seedu.address.model.person.Location; import seedu.address.model.person.Name; -import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Age; +import seedu.address.model.pet.Color; +import seedu.address.model.pet.ColorPattern; +import seedu.address.model.pet.DateOfBirth; +import seedu.address.model.pet.Height; +import seedu.address.model.pet.Pet; +import seedu.address.model.pet.PetCertificate; +import seedu.address.model.pet.PetName; +import seedu.address.model.pet.Species; +import seedu.address.model.pet.VaccinationStatus; +import seedu.address.model.pet.Weight; /** * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; + + //Buyers + public static final Buyer ALEX = new Buyer(new Name("Alex"), + new Phone("87438807"), new Email("alexyeoh@gmail.com"), new Address("Blk 30 Geylang Street 29, #06-40"), + new Location("Singapore"), new ArrayList<>()); + public static final Buyer BERNICE = new Buyer(new Name("Bernice Yu"), new Phone("99272758"), + new Email("berniceyu@firefox.com"), new Address("4/6 Huangpu Ave, Guangzhou"), + new Location("China"), new ArrayList<>()); + public static final Buyer CHARLOTTE = new Buyer(new Name("Charlotte Oliveiro"), + new Phone("93210283"), new Email("111charlotte@outlook.com"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + new Location("Singapore"), new ArrayList<>()); + public static final Buyer DAVID = new Buyer(new Name("David Li"), new Phone("91031282"), + new Email("lidav@outlook.com"), new Address("No 3 Burke Street 26, Victoria"), + new Location("Australia"), new ArrayList<>()); + public static final Buyer IRFAN = new Buyer(new Name("Irfan Ibrahim"), new Phone("92492021"), + new Email("irfan1989@firefox.com"), new Address("Blk 47 Tampines Street 20, #17-35"), + new Location("Singapore"), new ArrayList<>()); + public static final Buyer ROY = new Buyer(new Name("Roy Balakrishman"), new Phone("92624417"), + new Email("royb@yahoo.com"), new Address("Blk 45 Aljunied Street 85, #11-31"), + new Location("Singapore"), new ArrayList<>()); + + //Suppliers + public static final Supplier SHIN_CHAN = new Supplier(new Name("Shin Chan"), + new Phone("09594177555"), new Email("Shin-chan@crayon.com"), + new Address("The Nohara family household, Kasukabe"), + new Location("Singapore"), new ArrayList<>()); + public static final Supplier CONG = new Supplier(new Name("Cong Xu"), + new Phone("99272758"), new Email("cx973@gmail.com"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + new Location("Singapore"), new ArrayList<>()); + public static final Supplier EMMA = new Supplier(new Name("Emma Ang"), + new Phone("93210283"), new Email("e03389218@u.nus.edu"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + new Location("Singapore"), new ArrayList<>()); + public static final Supplier NGUYEN = new Supplier(new Name("Nguyen Duc Hong"), + new Phone("91031282"), new Email("ng4398@yandex.com"), + new Address("998 Nguyen Puh Duc Street, Ho Chi Minh City"), + new Location("Vietnam"), new ArrayList<>()); + public static final Supplier RAJ = new Supplier(new Name("Raj Pakashmala"), + new Phone("92492021"), new Email("pak0raj@gmail.com"), new Address("Blk 47 Tampines Street 20, #17-35"), + new Location("Singapore"), new ArrayList<>()); + public static final Supplier SMITH = new Supplier(new Name("Jack Smith"), + new Phone("92624417"), + new Email("jsmith@gmail.com"), new Address("10 Westminster Rd, Whitehall, West London"), + new Location("UK"), new ArrayList<>()); + + //Deliverers + public static final Deliverer ADI = new Deliverer(new Name("Adi Karma"), + new Phone("87438807"), + new Email("adiiiii@gmail.com"), new Address("Blk 30 Geylang Street 29, #06-40"), + new Location("Singapore"), new ArrayList<>()); + public static final Deliverer MINGMING = new Deliverer(new Name("Mingming Sun"), + new Phone("99272758"), + new Email("mmmmsun@outlook.com"), new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + new Location("Singapore"), new ArrayList<>()); + public static final Deliverer HAOLING = new Deliverer(new Name("Haoling Wang"), + new Phone("93210283"), + new Email("wang000@outlook.com"), new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + new Location("Singapore"), new ArrayList<>()); + public static final Deliverer NOBEL = new Deliverer(new Name("Nobel Ong"), + new Phone("91031282"), + new Email("nobongong@u.nus.edu"), new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + new Location("Singapore"), new ArrayList<>()); + public static final Deliverer YUSOF = new Deliverer(new Name("Yusof Ibrahim"), + new Phone("92492021"), + new Email("yusofff9066@outlook.com"), new Address("Blk 47 Tampines Street 20, #17-35"), + new Location("Singapore"), new ArrayList<>()); + public static final Deliverer FRANK = new Deliverer(new Name("Frank Appleby"), + new Phone("92624417"), + new Email("ffapp@outlook.com"), new Address("Blk 45 Aljunied Street 85, #11-31"), + new Location("Singapore"), new ArrayList<>()); + + //Orders + public static final Order O1 = getOrder(ALEX, 90.5, 50, + getRequest(2, "black", "royal blue and brown", "Persian cat"), + getAdditionalRequests("free delivery", "fast delivery", "quick deal"), + "2022-10-01", 80, OrderStatus.PENDING.toString()); + public static final Order O2 = getOrder(BERNICE, 15.0, 10.2, + getRequest(4, "white", "blue and pink", "German shepherd"), + getAdditionalRequests("free pet food", "discount", "fast delivery"), + "2022-11-20", -1, OrderStatus.NEGOTIATING.toString()); + public static final Order O3 = getOrder(CHARLOTTE, -1, -1, + getRequest(7, "lemon", "charcoal and yellow", "chihuahua"), + getAdditionalRequests("healthy", "no infectious disease", "clean"), + "2022-12-21", 12.5, OrderStatus.PENDING.toString()); + public static final Order O4 = getOrder(DAVID, 200, -1, + getRequest(0, "yellow", "red and yellow", "golden retriever"), + getAdditionalRequests("house-trained", "healthy", "free delivery"), + "2023-01-11", 112.5, OrderStatus.DELIVERING.toString()); + public static final Order O5 = getOrder(IRFAN, 90, 80, + getRequest(1, "blue", "lime green and electric blue", "American bobtail cat"), + getAdditionalRequests("quick deal", "no louses"), + "2022-12-08", 89.5, OrderStatus.NEGOTIATING.toString()); + public static final Order O6 = getOrder(ROY, 129, 110, + getRequest(2, "grey", "lavender and teal", "Javanese cat"), + getAdditionalRequests("discount"), + "2022-01-04", 123.5, OrderStatus.DELIVERING.toString()); + + //Pets + private static final int NOW = 2022; + public static final Pet SHIRO = new Pet(new PetName("Shiro"), SHIN_CHAN, new Color("black"), + new ColorPattern("royal blue and brown"), + getDateOfBirthForAge(2), new Species("Persian cat"), new Weight(27.9), + new Height(34.0), new VaccinationStatus(true), + new Price(1000), new HashSet<>()); + public static final Pet ASHY = new Pet(new PetName("Ashy"), CONG, new Color("grey"), + new ColorPattern("white and brown"), + getDateOfBirthForAge(3), new Species("Javanese cat"), new Weight(20.8), + new Height(38.9), new VaccinationStatus(true), + new Price(32), new HashSet<>()); + public static final Pet PLUM = new Pet(new PetName("Plum"), EMMA, new Color("light brown"), + new ColorPattern("royal blue and brown"), + getDateOfBirthForAge(2), new Species("chihuahua"), new Weight(10.05), + new Height(19.5), new VaccinationStatus(false), + new Price(80), new HashSet<>()); + public static final Pet PAGE = new Pet(new PetName("Page"), NGUYEN, new Color("brown"), + new ColorPattern("light brown"), + getDateOfBirthForAge(4), new Species("Persian cat"), new Weight(32.1), + new Height(36.7), new VaccinationStatus(false), + new Price(50), new HashSet<>()); + public static final Pet SNOWY = new Pet(new PetName("Snowy"), EMMA, new Color("black"), + new ColorPattern("royal blue and brown"), + getDateOfBirthForAge(4), new Species("Persian cat"), new Weight(24.1), + new Height(21.2), new VaccinationStatus(true), + new Price(90), new HashSet<>()); + public static final Pet BUDDY = new Pet(new PetName("Buddy"), EMMA, new Color("grey"), + new ColorPattern("white and brown"), + getDateOfBirthForAge(1), new Species("golden retriever"), new Weight(30.1), + new Height(27.4), new VaccinationStatus(true), + new Price(7777.77), new HashSet<>()); + + + public static Buyer[] getSampleBuyers() { + return new Buyer[] {ALEX, BERNICE, CHARLOTTE, DAVID, IRFAN, ROY}; + } + + public static Supplier[] getSampleSuppliers() { + return new Supplier[]{SHIN_CHAN, CONG, EMMA, NGUYEN, RAJ, SMITH}; + } + + public static Deliverer[] getSampleDeliverers() { + return new Deliverer[] {ADI, MINGMING, HAOLING, NOBEL, YUSOF, FRANK}; + } + + public static Order[] getSampleOrders() { + return new Order[] {O1, O2, O3, O4, O5, O6}; + } + + public static Pet[] getSamplePets() { + return new Pet[] {SHIRO, ASHY, PLUM, PAGE, SNOWY, BUDDY}; + } + + private static DateOfBirth getDateOfBirthForAge(int age) { + int diff = NOW - age; + try { + return DateOfBirth.parseString(diff + "-10-29"); + } catch (IllegalValueException e) { + throw new RuntimeException(e); + } } public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + + Buyer[] buyers = getSampleBuyers(); + Supplier[] suppliers = getSampleSuppliers(); + Order[] orders = getSampleOrders(); + Pet[] pets = getSamplePets(); + + for (Order o : orders) { + Buyer buyer = o.getBuyer(); + List tmp = new ArrayList<>(); + tmp.add(o.getId()); + buyer.addOrders(tmp); + } + + for (Pet p: pets) { + Supplier supplier = p.getSupplier(); + List tmp = new ArrayList<>(); + tmp.add(p.getId()); + supplier.addPets(tmp); + } + + for (Buyer sampleBuyer :buyers) { + sampleAb.addBuyer(sampleBuyer); + } + for (Supplier sampleSupplier : suppliers) { + sampleAb.addSupplier(sampleSupplier); + } + for (Deliverer sampleDeliverer : getSampleDeliverers()) { + sampleAb.addDeliverer(sampleDeliverer); + } + for (Order sampleOrder : orders) { + sampleAb.addOrder(sampleOrder); } + + for (Pet samplePet : getSamplePets()) { + sampleAb.addPet(samplePet); + } + return sampleAb; } /** - * Returns a tag set containing the list of strings given. + * Returns a certificate set containing the list of strings given. */ - public static Set getTagSet(String... strings) { + public static Set getCertificateSet(String... strings) { return Arrays.stream(strings) - .map(Tag::new) + .map(PetCertificate::new) .collect(Collectors.toSet()); } + public static Order getOrder(Buyer buyer, double upperBound, double lowerBound, Request request, + AdditionalRequests additionalRequests, String byDate, + double settledPrice, String status) { + PriceRange priceRange = new PriceRange(new Price(lowerBound), new Price(upperBound)); + Request orderRequest = request; + AdditionalRequests orderAdditionalRequests = additionalRequests; + LocalDate orderDate = LocalDate.parse(byDate); + Price orderSettledPrice = new Price(settledPrice); + OrderStatus orderStatus = Arrays.stream(OrderStatus.class.getEnumConstants()) + .filter(x -> x.toString().equals(status)) + .findFirst() + .orElse(OrderStatus.PENDING); + return new Order(buyer, priceRange, orderRequest, orderAdditionalRequests, orderDate, orderSettledPrice, + orderStatus); + } + + public static Request getRequest(int age, String color, String colorPattern, String species) { + Age modelAge = new Age(age); + Color modelColor = new Color(color); + ColorPattern modelColorPattern = new ColorPattern(colorPattern); + Species modelSpecies = new Species(species); + return new Request(modelAge, modelColor, modelColorPattern, modelSpecies); + } + + public static AdditionalRequests getAdditionalRequests(String... strings) { + return new AdditionalRequests(strings); + } + } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedBuyer.java b/src/main/java/seedu/address/storage/JsonAdaptedBuyer.java new file mode 100644 index 00000000000..c3f58ae71dc --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedBuyer.java @@ -0,0 +1,133 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.core.index.UniqueId; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Address; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Email; +import seedu.address.model.person.Location; +import seedu.address.model.person.Name; +import seedu.address.model.person.PersonCategory; +import seedu.address.model.person.Phone; + +/** + * Jackson-friendly version of {@link Buyer}. + */ +class JsonAdaptedBuyer { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Buyer's %s field is missing!"; + + private final String personCategory; + private final String name; + private final String phone; + private final String email; + private final String address; + private final String location; + private final List ids = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedBuyer} with the given Buyer details. + */ + @JsonCreator + public JsonAdaptedBuyer(@JsonProperty("personCategory") String personCategory, @JsonProperty("name") String name, + @JsonProperty("phone") String phone, @JsonProperty("email") String email, + @JsonProperty("address") String address, + @JsonProperty("location") String location, + @JsonProperty("ids") List ids) { + this.personCategory = personCategory; + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.location = location; + if (ids != null) { + this.ids.addAll(ids); + } + } + + /** + * Converts a given {@code Buyer} into this class for Jackson use. + */ + public JsonAdaptedBuyer(Buyer source) { + personCategory = source.getPersonCategory().toString(); + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + location = source.getLocation().location; + ids.addAll(source.getOrderIds().stream() + .map(UniqueId::getIdToString) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted buyer object into the model's {@code Buyer} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted buyer. + */ + public Buyer toModelType() throws IllegalValueException { + final ArrayList modelIds = new ArrayList<>(); + for (String id : ids) { + if (id == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + UniqueId.class.getSimpleName())); + } + modelIds.add(new UniqueId(id)); + } + + if (personCategory == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!PersonCategory.isValidPersonCategory(personCategory)) { + throw new IllegalValueException(PersonCategory.MESSAGE_CONSTRAINTS); + } + final PersonCategory modelPersonCategory = PersonCategory.getFromString(personCategory); + + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(phone)) { + throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); + } + final Phone modelPhone = new Phone(phone); + + if (email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(email)) { + throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); + } + final Email modelEmail = new Email(email); + + if (address == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + } + if (!Address.isValidAddress(address)) { + throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + } + final Address modelAddress = new Address(address); + + if (location == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Location.class.getSimpleName())); + } + final Location modelLocation = new Location(location); + + return new Buyer(modelName, modelPhone, modelEmail, modelAddress, modelLocation, modelIds); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedDeliverer.java b/src/main/java/seedu/address/storage/JsonAdaptedDeliverer.java new file mode 100644 index 00000000000..36cad89ec4c --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedDeliverer.java @@ -0,0 +1,134 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.core.index.UniqueId; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Address; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Email; +import seedu.address.model.person.Location; +import seedu.address.model.person.Name; +import seedu.address.model.person.PersonCategory; +import seedu.address.model.person.Phone; + +/** + * Jackson-friendly version of {@link Deliverer}. + */ +class JsonAdaptedDeliverer { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Deliverer 's %s field is missing!"; + + private final String personCategory; + private final String name; + private final String phone; + private final String email; + private final String address; + private final String location; + private final List ids = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedDeliverer} with the given Deliverer details. + */ + @JsonCreator + public JsonAdaptedDeliverer(@JsonProperty("personCategory") String personCategory, + @JsonProperty("name") String name, @JsonProperty("phone") String phone, + @JsonProperty("email") String email, @JsonProperty("address") String address, + @JsonProperty("location") String location, + @JsonProperty("ids") List ids) { + this.personCategory = personCategory; + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.location = location; + if (ids != null) { + this.ids.addAll(ids); + } + } + + /** + * Converts a given {@code Deliverer} into this class for Jackson use. + */ + public JsonAdaptedDeliverer(Deliverer source) { + personCategory = source.getPersonCategory().toString(); + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + location = source.getLocation().location; + ids.addAll(source.getOrders().stream() + .map(UniqueId::getIdToString) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted Deliverer object into the model's {@code Deliverer} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted Deliverer . + */ + public Deliverer toModelType() throws IllegalValueException { + final ArrayList modelIds = new ArrayList<>(); + for (String id : ids) { + if (id == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + UniqueId.class.getSimpleName())); + } + modelIds.add(new UniqueId(id)); + } + + if (personCategory == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + PersonCategory.class.getSimpleName())); + } + if (!PersonCategory.isValidPersonCategory(personCategory)) { + throw new IllegalValueException(PersonCategory.MESSAGE_CONSTRAINTS); + } + final PersonCategory modelPersonCategory = PersonCategory.getFromString(personCategory); + + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(phone)) { + throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); + } + final Phone modelPhone = new Phone(phone); + + if (email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(email)) { + throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); + } + final Email modelEmail = new Email(email); + + if (address == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + } + if (!Address.isValidAddress(address)) { + throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + } + final Address modelAddress = new Address(address); + + if (location == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Location.class.getSimpleName())); + } + final Location modelLocation = new Location(location); + + return new Deliverer(modelName, modelPhone, modelEmail, modelAddress, modelLocation, modelIds); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedOrder.java b/src/main/java/seedu/address/storage/JsonAdaptedOrder.java new file mode 100644 index 00000000000..379b15b7438 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedOrder.java @@ -0,0 +1,149 @@ +package seedu.address.storage; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.core.index.UniqueId; +import seedu.address.commons.core.index.UniqueIdGenerator; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.order.AdditionalRequests; +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderStatus; +import seedu.address.model.order.Price; +import seedu.address.model.order.PriceRange; +import seedu.address.model.order.Request; +import seedu.address.model.person.Buyer; + +/** + * Jackson-friendly version of {@link Order}. + */ +class JsonAdaptedOrder { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Order's %s field is missing!"; + + private final JsonAdaptedBuyer buyer; + private final JsonAdaptedPriceRange requestedPriceRange; + private final JsonAdaptedRequest request; + private final List additionalRequests = new ArrayList<>(); + private final String byDate; + private final Double settledPrice; + private final String status; + private final String uniqueId; + + /** + * Constructs a {@code JsonAdaptedOrder} with the given Order details. + */ + @JsonCreator + public JsonAdaptedOrder(@JsonProperty("buyer") JsonAdaptedBuyer buyer, + @JsonProperty("range") JsonAdaptedPriceRange range, + @JsonProperty("request") JsonAdaptedRequest request, + @JsonProperty("additional") List additional, + @JsonProperty("byDate") String byDate, + @JsonProperty("settledPrice") Double settledPrice, + @JsonProperty("status") String status, + @JsonProperty("uniqueId") String uniqueId) { + this.buyer = buyer; + this.requestedPriceRange = range; + this.request = request; + if (additional != null) { + this.additionalRequests.addAll(additional); + } + this.byDate = byDate; + this.settledPrice = settledPrice; + this.status = status; + this.uniqueId = uniqueId; + } + + /** + * Converts a given {@code Order} into this class for Jackson use. + */ + public JsonAdaptedOrder(Order order) { + this.buyer = new JsonAdaptedBuyer(order.getBuyer()); + this.requestedPriceRange = new JsonAdaptedPriceRange(order.getRequestedPriceRange()); + this.request = new JsonAdaptedRequest(order.getRequest()); + this.additionalRequests.addAll(order.getAdditionalRequests().getAdditionalRequestsToString()); + this.byDate = order.getByDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + this.settledPrice = order.getSettledPrice().getPrice(); + this.status = order.getOrderStatus().getStatus(); + this.uniqueId = order.getId().getIdToString(); + } + + /** + * Converts this Jackson-friendly adapted order object into the model's {@code Order} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted order. + */ + public Order toModelType() throws IllegalValueException { + if (buyer == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Buyer.class.getSimpleName())); + } + Buyer modelBuyer = buyer.toModelType(); + + if (requestedPriceRange == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + PriceRange.class.getSimpleName())); + } + PriceRange modelPriceRange = requestedPriceRange.toModelType(); + + if (request == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Request.class.getSimpleName())); + } + Request modelRequest = request.toModelType(); + + if (additionalRequests == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + AdditionalRequests.class.getSimpleName())); + } + AdditionalRequests modelAdditionalRequest = new AdditionalRequests(additionalRequests.toArray(new String[0])); + + if (byDate == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + LocalDate.class.getSimpleName())); + } + + LocalDate modelByDate; + try { + modelByDate = LocalDate.parse(byDate); + } catch (DateTimeParseException e) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + LocalDate.class.getSimpleName())); + } + + if (settledPrice == null || (!Price.isNotApplicablePrice(new Price(settledPrice)) + && settledPrice < 0)) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Price.class.getSimpleName())); + } + Price modelPrice = new Price(settledPrice); + + if (status == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + OrderStatus.class.getSimpleName())); + } + OrderStatus modelOrderStatus = Arrays.stream(OrderStatus.class.getEnumConstants()) + .filter(x -> x.toString().equals(status)) + .findFirst() + .orElse(OrderStatus.PENDING); + + if (uniqueId == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + UniqueId.class.getSimpleName())); + } + UniqueId modelUniqueId = new UniqueId(uniqueId); + if (UniqueIdGenerator.storedIdOrderContains(modelUniqueId)) { + throw new IllegalValueException("Repeated unique id for pet"); + } + return new Order(modelBuyer, modelPriceRange, modelRequest, modelAdditionalRequest, modelByDate, modelPrice, + modelOrderStatus, modelUniqueId); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2ea..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tagged = new ArrayList<>(); - - /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. - */ - @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson use. - */ - public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged.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 Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPet.java b/src/main/java/seedu/address/storage/JsonAdaptedPet.java new file mode 100644 index 00000000000..2c4f0c34976 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedPet.java @@ -0,0 +1,170 @@ +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.core.index.UniqueId; +import seedu.address.commons.core.index.UniqueIdGenerator; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.order.Price; +import seedu.address.model.person.Name; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Color; +import seedu.address.model.pet.ColorPattern; +import seedu.address.model.pet.DateOfBirth; +import seedu.address.model.pet.Height; +import seedu.address.model.pet.Pet; +import seedu.address.model.pet.PetCertificate; +import seedu.address.model.pet.PetName; +import seedu.address.model.pet.Species; +import seedu.address.model.pet.VaccinationStatus; +import seedu.address.model.pet.Weight; + +/** + * Jackson-friendly version of {@link Pet}. + */ +public class JsonAdaptedPet { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Pet's %s field is missing!"; + + private final String name; + private final JsonAdaptedSupplier supplier; + private final String color; + private final String colorPattern; + private final String dateOfBirth; + private final String species; + private final Double weight; + private final Double height; + private final boolean vaccinationStatus; + private final Double price; + private final List certificates = new ArrayList<>(); + private final String uniqueId; + + /** + * Constructs a {@code JsonAdaptedPet} with the given Pet details. + */ + @JsonCreator + public JsonAdaptedPet(@JsonProperty("name") String name, + @JsonProperty("supplier") JsonAdaptedSupplier supplier, + @JsonProperty("color") String color, + @JsonProperty("colorPattern") String colorPattern, + @JsonProperty("dateOfBirth") String dateOfBirth, + @JsonProperty("species") String species, + @JsonProperty("weight") Double weight, + @JsonProperty("height") Double height, + @JsonProperty("vaccinationStatus") boolean vaccinationStatus, + @JsonProperty("price") Double price, + @JsonProperty("certificates") List certificates, + @JsonProperty("uniqueId") String uniqueId) { + this.name = name; + this.supplier = supplier; + this.color = color; + this.colorPattern = colorPattern; + this.dateOfBirth = dateOfBirth; + this.species = species; + this.weight = weight; + this.height = height; + this.vaccinationStatus = vaccinationStatus; + this.price = price; + if (certificates != null) { + this.certificates.addAll(certificates); + } + this.uniqueId = uniqueId; + } + + /** + * Converts a given {@code Pet} into this class for Jackson use. + */ + public JsonAdaptedPet(Pet source) { + name = source.getName().toString(); + supplier = new JsonAdaptedSupplier(source.getSupplier()); + color = source.getColor().getValue(); + colorPattern = source.getColorPattern().getValue(); + dateOfBirth = source.getDateOfBirth().getPreferredDateInString(); + species = source.getSpecies().getValue(); + weight = source.getWeight().getValue(); + height = source.getHeight().getValue(); + vaccinationStatus = source.getVaccinationStatus().getVaccinationStatus(); + price = source.getPrice().getPrice(); + certificates.addAll(source.getCertificates().stream() + .map(PetCertificate::toString).collect(Collectors.toList())); + uniqueId = source.getId().getIdToString(); + } + + /** + * Converts this Jackson-friendly adapted Pet object into the model's {@code Pet} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted pet. + */ + public Pet toModelType() throws IllegalValueException { + + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!PetName.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final PetName modelName = new PetName(name); + + if (supplier == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Supplier.class.getSimpleName())); + } + final Supplier modelSupplier = supplier.toModelType(); + + final Color modelColor = new Color(color); + + final ColorPattern modelColorPattern = new ColorPattern(colorPattern); + + if (dateOfBirth == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DateOfBirth.class.getSimpleName())); + } + final DateOfBirth modelDateOfBirth = DateOfBirth.parseString(dateOfBirth); + + final Species modelSpecies = new Species(species); + + if (weight == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Weight.class.getSimpleName())); + } + final Weight modelWeight = new Weight(weight); + + if (height == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Height.class.getSimpleName())); + } + final Height modelHeight = new Height(height); + + final VaccinationStatus modelVax = new VaccinationStatus(vaccinationStatus); + + if (price == null || (!Price.isNotApplicablePrice(new Price(price)) && price < 0)) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Price.class.getSimpleName())); + } + final Price modelPrice = new Price(price); + + final Set modelCerts = new HashSet<>(); + if (certificates == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + PetCertificate.class.getSimpleName())); + } + for (String cert : certificates) { + modelCerts.add(new PetCertificate(cert)); + } + + if (uniqueId == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + UniqueId.class.getSimpleName())); + } + UniqueId modelUniqueId = new UniqueId(uniqueId); + if (UniqueIdGenerator.storedIdPetContains(modelUniqueId)) { + throw new IllegalValueException("Repeated unique id for pet"); + } + + return new Pet(modelUniqueId, modelName, modelSupplier, modelColor, modelColorPattern, modelDateOfBirth, + modelSpecies, modelWeight, modelHeight, modelVax, modelPrice, modelCerts); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPriceRange.java b/src/main/java/seedu/address/storage/JsonAdaptedPriceRange.java new file mode 100644 index 00000000000..b0d8b2cef22 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedPriceRange.java @@ -0,0 +1,45 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.Price; +import seedu.address.model.order.PriceRange; + +/** + * Jackson-friendly version of {@link PriceRange}. + */ +public class JsonAdaptedPriceRange { + + private Double upperBound; + private Double lowerBound; + + /** + * Constructs a {@code JsonAdaptedPriceRange} with the given upperbound and lowerbound. + */ + public JsonAdaptedPriceRange(@JsonProperty("upperBound") Double upperBound, + @JsonProperty("lowerBound") Double lowerBound) { + this.upperBound = upperBound; + this.lowerBound = lowerBound; + } + + /** + * Converts a given {@code PriceRange} into this class for Jackson use. + */ + public JsonAdaptedPriceRange(PriceRange requestedPriceRange) { + this.upperBound = requestedPriceRange.getUpperBound().getPrice(); + this.lowerBound = requestedPriceRange.getLowerBound().getPrice(); + } + + /** + * Converts this Jackson-friendly adapted PriceRange object into the model's {@code PriceRange} object. + */ + public PriceRange toModelType() throws ParseException { + if (lowerBound == null || upperBound == null || lowerBound < -1 || upperBound < -1) { + throw new ParseException(PriceRange.MESSAGE_USAGE); + } + Price modelUpperBound = new Price(upperBound); + Price modelLowerBound = new Price(lowerBound); + return new PriceRange(modelLowerBound, modelUpperBound); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedRequest.java b/src/main/java/seedu/address/storage/JsonAdaptedRequest.java new file mode 100644 index 00000000000..f2f2574d6e3 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedRequest.java @@ -0,0 +1,71 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.order.Request; +import seedu.address.model.pet.Age; +import seedu.address.model.pet.Color; +import seedu.address.model.pet.ColorPattern; +import seedu.address.model.pet.Species; + +/** + * Jackson-friendly version of {@link Request}. + */ +public class JsonAdaptedRequest { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Request's %s field is missing!"; + + private final int age; + private final String color; + private final String colorPattern; + private final String species; + + /** + * Constructs a {@code JsonAdaptedRequest} with the given request details. + */ + public JsonAdaptedRequest(@JsonProperty("age") int age, @JsonProperty("color") String color, + @JsonProperty("colorPattern") String colorPattern, + @JsonProperty("species") String species) { + this.age = age; + this.color = color; + this.colorPattern = colorPattern; + this.species = species; + } + + /** + * Converts a given {@code Request} into this class for Jackson use. + */ + public JsonAdaptedRequest(Request request) { + this.age = request.getRequestedAge().getValue(); + this.color = request.getRequestedColor().getValue(); + this.colorPattern = request.getRequestedColorPattern().getValue(); + this.species = request.getRequestedSpecies().getValue(); + } + + /** + * Converts this Jackson-friendly adapted Request object into the model's {@code Request} object. + */ + public Request toModelType() throws IllegalValueException { + if (age < 0) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Age.class.getName())); + } + + if (color == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Color.class.getName())); + } + if (colorPattern == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, ColorPattern.class.getName())); + } + + if (species == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Species.class.getName())); + } + + Age modelAge = new Age(age); + Color modelColor = new Color(color); + ColorPattern modelColorPattern = new ColorPattern(colorPattern); + Species modelSpecies = new Species(species); + return new Request(modelAge, modelColor, modelColorPattern, modelSpecies); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedSupplier.java b/src/main/java/seedu/address/storage/JsonAdaptedSupplier.java new file mode 100644 index 00000000000..f9b0c8462df --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedSupplier.java @@ -0,0 +1,133 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.core.index.UniqueId; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Location; +import seedu.address.model.person.Name; +import seedu.address.model.person.PersonCategory; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Supplier; + +/** + * Jackson-friendly version of {@link Supplier}. + */ +class JsonAdaptedSupplier { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Supplier's %s field is missing!"; + + private final String personCategory; + private final String name; + private final String phone; + private final String email; + private final String address; + private final String location; + private final List ids = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedSupplier} with the given Supplier details. + */ + @JsonCreator + public JsonAdaptedSupplier(@JsonProperty("personCategory") String personCategory, @JsonProperty("name") String name, + @JsonProperty("phone") String phone, @JsonProperty("email") String email, + @JsonProperty("address") String address, + @JsonProperty("location") String location, + @JsonProperty("ids") List ids) { + this.personCategory = personCategory; + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.location = location; + if (ids != null) { + this.ids.addAll(ids); + } + } + + /** + * Converts a given {@code Supplier} into this class for Jackson use. + */ + public JsonAdaptedSupplier(Supplier source) { + personCategory = source.getPersonCategory().toString(); + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + location = source.getLocation().location; + ids.addAll(source.getPetIds().stream() + .map(UniqueId::getIdToString) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted Supplier object into the model's {@code Supplier} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted Supplier. + */ + public Supplier toModelType() throws IllegalValueException { + + final ArrayList modelIds = new ArrayList<>(); + for (String id : ids) { + if (id == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + UniqueId.class.getSimpleName())); + } + modelIds.add(new UniqueId(id)); + } + + if (personCategory == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!PersonCategory.isValidPersonCategory(personCategory)) { + throw new IllegalValueException(PersonCategory.MESSAGE_CONSTRAINTS); + } + + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(phone)) { + throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); + } + final Phone modelPhone = new Phone(phone); + + if (email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(email)) { + throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); + } + final Email modelEmail = new Email(email); + + if (address == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + } + if (!Address.isValidAddress(address)) { + throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + } + final Address modelAddress = new Address(address); + + if (location == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Location.class.getSimpleName())); + } + final Location modelLocation = new Location(location); + + return new Supplier(modelName, modelPhone, modelEmail, modelAddress, modelLocation, modelIds); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java deleted file mode 100644 index 0df22bdb754..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.storage; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Tag}. - */ -class JsonAdaptedTag { - - private final String tagName; - - /** - * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. - */ - @JsonCreator - public JsonAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given {@code Tag} into this class for Jackson use. - */ - public JsonAdaptedTag(Tag source) { - tagName = source.tagName; - } - - @JsonValue - public String getTagName() { - return tagName; - } - - /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(tagName); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..d2c799342e1 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -11,7 +11,11 @@ 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.order.Order; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; /** * An Immutable AddressBook that is serializable to JSON format. @@ -19,16 +23,32 @@ @JsonRootName(value = "addressbook") class JsonSerializableAddressBook { - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_BUYER = "Buyers list contains duplicate buyer(s)."; + public static final String MESSAGE_DUPLICATE_SUPPLIER = "Suppliers list contains duplicate supplier(s)."; + public static final String MESSAGE_DUPLICATE_DELIEVER = "Deliverers list contains duplicate deliverer(s)."; + public static final String MESSAGE_DUPLICATE_PET = "Pets list contains duplicate pet(s)."; + public static final String MESSAGE_DUPLICATE_ORDER = "Orders list contains duplicate order(s)."; - private final List persons = new ArrayList<>(); + private final List buyers = new ArrayList<>(); + private final List suppliers = new ArrayList<>(); + private final List deliverers = new ArrayList<>(); + private final List orders = new ArrayList<>(); + private final List pets = new ArrayList<>(); /** * Constructs a {@code JsonSerializableAddressBook} with the given persons. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); + public JsonSerializableAddressBook(@JsonProperty("buyers") List buyers, + @JsonProperty("suppliers") List suppliers, + @JsonProperty("deliverers") List deliverers, + @JsonProperty("orders") List orders, + @JsonProperty("pets") List pets) { + this.buyers.addAll(buyers); + this.suppliers.addAll(suppliers); + this.deliverers.addAll(deliverers); + this.orders.addAll(orders); + this.pets.addAll(pets); } /** @@ -37,7 +57,12 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = + "https://github.com/AY2223S1-CS2103T-T09-2/tp/blob/master/docs/UserGuide.md"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..b3ea0550b6e 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -4,18 +4,23 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.geometry.Rectangle2D; import javafx.scene.control.MenuItem; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; +import javafx.stage.Screen; import javafx.stage.Stage; +import javafx.stage.StageStyle; 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.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.ui.listpanels.MainListPanel; +import seedu.address.ui.popupwindow.AddCommandPopupWindow; /** * The Main Window. Provides the basic application layout containing @@ -27,13 +32,15 @@ public class MainWindow extends UiPart { private final Logger logger = LogsCenter.getLogger(getClass()); - private Stage primaryStage; - private Logic logic; + private final Stage primaryStage; + private final Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private MainListPanel currListPanel; + private ResultDisplay resultDisplay; - private HelpWindow helpWindow; + private final HelpWindow helpWindow; + private AddCommandPopupWindow addCommandPopupWindow; @FXML private StackPane commandBoxPlaceholder; @@ -48,7 +55,10 @@ public class MainWindow extends UiPart { private StackPane resultDisplayPlaceholder; @FXML - private StackPane statusbarPlaceholder; + private StackPane statusBarPlaceholder; + + @FXML + private StackPane selectionBoxPlaceHolder; /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. @@ -84,7 +94,7 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { menuItem.setAccelerator(keyCombination); /* - * TODO: the code below can be removed once the bug reported here + * The code below can be removed once the bug reported here * https://bugs.openjdk.java.net/browse/JDK-8131666 * is fixed in later version of SDK. * @@ -110,15 +120,14 @@ 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()); + // Initialise the list panels + // Set the display window + refresh(); + // Initialise the remaining components in the main window resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); - statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); - CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); } @@ -148,6 +157,14 @@ public void handleHelp() { } void show() { + Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds(); + + primaryStage.initStyle(StageStyle.DECORATED); + primaryStage.setWidth(screenBounds.getWidth()); + primaryStage.setHeight(screenBounds.getHeight()); + primaryStage.setX(screenBounds.getMinX()); + primaryStage.setY(screenBounds.getMinY()); + primaryStage.setMaxWidth(screenBounds.getWidth() * 2); primaryStage.show(); } @@ -159,12 +176,32 @@ private void handleExit() { GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), (int) primaryStage.getX(), (int) primaryStage.getY()); logic.setGuiSettings(guiSettings); + if (addCommandPopupWindow != null) { + addCommandPopupWindow.close(); + } helpWindow.hide(); primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + /** + * Refreshes the current list. + */ + public void refresh() { + currListPanel = new MainListPanel(logic.getFilteredCurrList(), logic); + personListPanelPlaceholder.getChildren().clear(); + personListPanelPlaceholder.getChildren().add(currListPanel.getRoot()); + } + + /** + * Creates a pop-up window. + * + * @param addType Typo of person to be added. + */ + public void handleAddByPopup(String addType) { + addCommandPopupWindow = new AddCommandPopupWindow(logic, addType, resultDisplay, currListPanel, + personListPanelPlaceholder); + addCommandPopupWindow.show(); + addCommandPopupWindow.fillContentPlaceholder(addType); } /** @@ -175,10 +212,11 @@ public PersonListPanel getPersonListPanel() { private CommandResult executeCommand(String commandText) throws CommandException, ParseException { try { CommandResult commandResult = logic.execute(commandText); + refresh(); logger.info("Result: " + commandResult.getFeedbackToUser()); resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); - if (commandResult.isShowHelp()) { + if (commandResult.isHelpShown()) { handleHelp(); } @@ -186,11 +224,17 @@ private CommandResult executeCommand(String commandText) throws CommandException handleExit(); } + if (commandResult.isAddedByPopup()) { + handleAddByPopup(commandResult.getAddType()); + } + return commandResult; + } catch (CommandException | ParseException e) { logger.info("Invalid command: " + commandText); resultDisplay.setFeedbackToUser(e.getMessage()); throw e; } } + } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index 7fc927bc5d9..00000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,77 +0,0 @@ -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.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.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 Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. - */ - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - 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() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} 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/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index fdf024138bc..5a68742f6df 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -20,7 +20,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/petCode.png"; private Logic logic; private MainWindow mainWindow; @@ -39,6 +39,7 @@ public void start(Stage primaryStage) { //Set the application icon. primaryStage.getIcons().add(getImage(ICON_APPLICATION)); + try { mainWindow = new MainWindow(primaryStage, logic); mainWindow.show(); //This should be called before creating other UI parts diff --git a/src/main/java/seedu/address/ui/displaycards/BuyerCard.java b/src/main/java/seedu/address/ui/displaycards/BuyerCard.java new file mode 100644 index 00000000000..39a44b98f26 --- /dev/null +++ b/src/main/java/seedu/address/ui/displaycards/BuyerCard.java @@ -0,0 +1,117 @@ +package seedu.address.ui.displaycards; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.logic.Logic; +import seedu.address.model.order.Order; +import seedu.address.model.person.Buyer; +import seedu.address.ui.UiPart; + +/** + * An UI component that displays information of a {@code Buyer}. + */ +public class BuyerCard extends UiPart { + + private static final String FXML = "BuyerListCard.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 + */ + + private final Logic logic; + private final Buyer buyer; + private final int displayedIndex; + + @FXML + private Label address; + + @FXML + private Label email; + + @FXML + private Label id; + + @FXML + private Label locatedCountry; + + @FXML + private Label name; + + @FXML + private ListView orderListView; + + @FXML + private Label phone; + + /** + * Creates a {@code BuyerCode} with the given {@code Buyer} and index to display. + */ + public BuyerCard(Buyer buyer, int displayedIndex, Logic logic) { + super(FXML); + this.buyer = buyer; + this.displayedIndex = displayedIndex; + this.logic = logic; + fillBuyerCard(); + } + + /** + * Fills the relevant fields of the buyer card. + */ + public void fillBuyerCard() { + // Set the contact details + id.setText(displayedIndex + ". "); + name.setText(buyer.getName().fullName); + phone.setText(buyer.getPhone().value); + locatedCountry.setText(buyer.getLocation().location); + address.setText(buyer.getAddress().value); + email.setText(buyer.getEmail().value); + + // Set the buyer's orders in the list view + orderListView.setItems(logic.getOrderAsObservableListFromBuyer(buyer)); + orderListView.setCellFactory(listView -> new BuyerOrdersListViewCell()); + + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Order} using a {@code OrderCard}. + */ + private static class BuyerOrdersListViewCell extends ListCell { + @Override + protected void updateItem(Order order, boolean empty) { + super.updateItem(order, empty); + + if (empty || order == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new OrderCard(order, getIndex() + 1, OrderCard.SHOULD_NOT_DISPLAY_BUYER_NAME) + .getRoot()); + } + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof BuyerCard)) { + return false; + } + + // state check + BuyerCard card = (BuyerCard) other; + return id.getText().equals(card.id.getText()) + && buyer.equals(card.buyer); + } +} diff --git a/src/main/java/seedu/address/ui/displaycards/DelivererCard.java b/src/main/java/seedu/address/ui/displaycards/DelivererCard.java new file mode 100644 index 00000000000..a25c644b656 --- /dev/null +++ b/src/main/java/seedu/address/ui/displaycards/DelivererCard.java @@ -0,0 +1,115 @@ +package seedu.address.ui.displaycards; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.logic.Logic; +import seedu.address.model.order.Order; +import seedu.address.model.person.Deliverer; +import seedu.address.ui.UiPart; + +/** + * An UI component that displays information of a {@code Deliverer}. + */ +public class DelivererCard extends UiPart { + + private static final String FXML = "DelivererListCard.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 + */ + + private final Logic logic; + private final Deliverer deliverer; + private final int displayedIndex; + + @FXML + private Label address; + + @FXML + private Label email; + + @FXML + private Label id; + + @FXML + private Label locatedCountry; + + @FXML + private Label name; + + @FXML + private ListView orderListView; + + @FXML + private Label phone; + + /** + * Creates a {@code DelivererCode} with the given {@code Deliverer} and index to display. + */ + public DelivererCard(Deliverer deliverer, int displayedIndex, Logic logic) { + super(FXML); + this.deliverer = deliverer; + this.displayedIndex = displayedIndex; + this.logic = logic; + fillDeliverCard(); + } + + /** + * Fills the relevant fields of the deliverer card. + */ + public void fillDeliverCard() { + // Set the contact details + id.setText(displayedIndex + ". "); + name.setText(deliverer.getName().fullName); + phone.setText(deliverer.getPhone().value); + locatedCountry.setText(deliverer.getLocation().location); + address.setText(deliverer.getAddress().value); + email.setText(deliverer.getEmail().value); + + // Set the buyer's orders in the list view + orderListView.setItems(logic.getOrderAsObservableListFromDeliverer(deliverer)); + orderListView.setCellFactory(listView -> new DelivererOrdersListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Order} using a {@code BriefOrderCard}. + */ + private static class DelivererOrdersListViewCell extends ListCell { + @Override + protected void updateItem(Order order, boolean empty) { + super.updateItem(order, empty); + + if (empty || order == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new DelivererOrderCard(order, getIndex() + 1).getRoot()); + } + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DelivererCard)) { + return false; + } + + // state check + DelivererCard card = (DelivererCard) other; + return id.getText().equals(card.id.getText()) + && deliverer.equals(card.deliverer); + } +} diff --git a/src/main/java/seedu/address/ui/displaycards/DelivererOrderCard.java b/src/main/java/seedu/address/ui/displaycards/DelivererOrderCard.java new file mode 100644 index 00000000000..68cf3cdbf1a --- /dev/null +++ b/src/main/java/seedu/address/ui/displaycards/DelivererOrderCard.java @@ -0,0 +1,93 @@ +package seedu.address.ui.displaycards; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.order.Order; +import seedu.address.ui.UiPart; + +/** + * A UI component that displays information of a {@code Order} relevant to the deliverer. + */ +public class DelivererOrderCard extends UiPart { + + private static final String FXML = "DelivererOrderListCard.fxml"; + private final Order order; + private final int displayedIndex; + + @FXML + private Label age; + + @FXML + private Label buyerName; + + @FXML + private Label buyerPhone; + + @FXML + private Label color; + + @FXML + private Label colorPattern; + + @FXML + private Label estimatedArrivalDate; + + @FXML + private Label id; + + @FXML + private Label orderStatus; + + @FXML + private Label species; + + @FXML + private Label toAddress; + + /** + * Creates a {@code DelivererOrderCard} with the given {@code Order} and index to display. + */ + public DelivererOrderCard(Order order, int displayedIndex) { + super(FXML); + this.order = order; + this.displayedIndex = displayedIndex; + fillBriefOrderCard(); + } + + /** + * Fills the relevant fields in the brief order card. + */ + public void fillBriefOrderCard() { + // Set order information + id.setText("#" + displayedIndex); + buyerName.setText("from " + order.getBuyer().getName().fullName); + orderStatus.setText(order.getOrderStatus().getStatus()); + species.setText(order.getRequest().getRequestedSpecies().toString()); + age.setText(order.getRequest().getRequestedAge().toString()); + color.setText(order.getRequest().getRequestedColor().toString()); + colorPattern.setText(order.getRequest().getRequestedColorPattern().toString()); + + // Set buyer information for delivering + toAddress.setText("To: " + order.getBuyer().getAddress().value); + buyerPhone.setText("Buyer contact: " + order.getBuyer().getPhone().value); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DelivererOrderCard)) { + return false; + } + + // state check + DelivererOrderCard card = (DelivererOrderCard) other; + return id.getText().equals(card.id.getText()) + && order.equals(card.order); + } +} diff --git a/src/main/java/seedu/address/ui/displaycards/OrderCard.java b/src/main/java/seedu/address/ui/displaycards/OrderCard.java new file mode 100644 index 00000000000..c512863e3fb --- /dev/null +++ b/src/main/java/seedu/address/ui/displaycards/OrderCard.java @@ -0,0 +1,111 @@ +package seedu.address.ui.displaycards; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.order.Order; +import seedu.address.ui.UiPart; + +/** + * A UI component that displays information of a {@code Order}. + */ +public class OrderCard extends UiPart { + + public static final boolean SHOULD_DISPLAY_BUYER_NAME = true; + public static final boolean SHOULD_NOT_DISPLAY_BUYER_NAME = false; + private static final String FXML = "OrderListCard.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 + */ + + private final Order order; + private final int displayedIndex; + + @FXML + private Label additionalRequestsDescription; + + @FXML + private Label age; + + @FXML + private Label buyerName; + + @FXML + private Label byDate; + + @FXML + private Label color; + + @FXML + private Label colorPattern; + + @FXML + private Label id; + + @FXML + private Label orderStatus; + + @FXML + private Label priceRange; + + @FXML + private Label settledPrice; + + @FXML + private Label species; + + /** + * Creates a {@code OrderCard} with the given {@code Order} and index to display. + * The boolean indicates whether the buyer name should be displayed. + */ + public OrderCard(Order order, int displayedIndex, boolean isBuyerNameShown) { + super(FXML); + this.order = order; + this.displayedIndex = displayedIndex; + fillOrderCard(isBuyerNameShown); + } + + /** + * Fills the relevant fields in the order card. + * + * @param shouldDisplayBuyerName A boolean value indicating whether the buyer name should be displayed. + */ + public void fillOrderCard(boolean shouldDisplayBuyerName) { + if (shouldDisplayBuyerName) { + buyerName.setText("from " + order.getBuyer().getName().fullName); + } + id.setText("#" + displayedIndex); + orderStatus.setText(order.getOrderStatus().getStatus()); + species.setText(order.getRequest().getRequestedSpecies().toString()); + age.setText(order.getRequest().getRequestedAge().toString()); + color.setText(order.getRequest().getRequestedColor().toString()); + colorPattern.setText(order.getRequest().getRequestedColorPattern().toString()); + priceRange.setText(order.getRequestedPriceRange().toString()); + byDate.setText("Complete order by: " + order.getByDate()); + settledPrice.setText(order.getSettledPrice().toString()); + additionalRequestsDescription.setText(order.getAdditionalRequests().toString()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OrderCard)) { + return false; + } + + // state check + OrderCard card = (OrderCard) other; + return id.getText().equals(card.id.getText()) + && order.equals(card.order); + } +} diff --git a/src/main/java/seedu/address/ui/displaycards/PetCard.java b/src/main/java/seedu/address/ui/displaycards/PetCard.java new file mode 100644 index 00000000000..08ef265249d --- /dev/null +++ b/src/main/java/seedu/address/ui/displaycards/PetCard.java @@ -0,0 +1,153 @@ +package seedu.address.ui.displaycards; + +import java.awt.Desktop; +import java.awt.Rectangle; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Region; +import seedu.address.model.pet.Pet; +import seedu.address.ui.UiPart; + +/** + * An UI component that displays information of a {@code Pet}. + */ +public class PetCard extends UiPart { + + public static final boolean SHOULD_DISPLAY_SUPPLIER_NAME = true; + public static final boolean SHOULD_NOT_DISPLAY_SUPPLIER_NAME = false; + private static final String FXML = "PetListCard.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 + */ + + private final Pet pet; + private final int displayedIndex; + + @FXML + private Label age; + + @FXML + private Label color; + + @FXML + private Label colorPattern; + + @FXML + private Label dateOfBirth; + + @FXML + private Label height; + + @FXML + private Label id; + + @FXML + private Hyperlink petCertificates; + + @FXML + private Label petName; + + @FXML + private ImageView petPhoto; + + @FXML + private Rectangle petPhotoHolder; + + @FXML + private Label salePrice; + + @FXML + private Label species; + + @FXML + private Label supplierName; + + @FXML + private Hyperlink vaccinationProof; + + @FXML + private Label weight; + + /** + * Creates a {@code PetCard} with the given {@code Pet} and index to display. + * The boolean indicates whether the supplier name should be displayed. + */ + public PetCard(Pet pet, int displayedIndex, boolean isSupplierNameShown) { + super(FXML); + this.pet = pet; + this.displayedIndex = displayedIndex; + fillPetCard(isSupplierNameShown); + } + + /** + * Fills the relevant fields in the pet card. + * + * @param shouldDisplaySupplierName A boolean value indicating whether the suppler name should be displayed. + */ + public void fillPetCard(boolean shouldDisplaySupplierName) { + if (shouldDisplaySupplierName) { + supplierName.setText("from " + pet.getSupplier().getName().fullName); + } + id.setText("#" + displayedIndex); + species.setText(pet.getSpecies().getValue()); + petName.setText(pet.getName().fullName); + age.setText("Age: " + String.valueOf(pet.getAge())); + dateOfBirth.setText(pet.getDateOfBirth().toString()); + color.setText(pet.getColor().toString()); + colorPattern.setText(pet.getColorPattern().toString()); + height.setText(pet.getHeight().toString()); + weight.setText(pet.getWeight().toString()); + salePrice.setText("Price: " + pet.getPrice().getPrice()); + + // Set the pet photo to fill the holder + + // For now the image is just a dummy image + Image image = new Image(this.getClass().getResourceAsStream("/images/dummy_pet_image.png")); + petPhoto.setImage(image); + } + + @FXML + void handleCertificatesLink(ActionEvent event) throws URISyntaxException, IOException { + // For now + System.out.println("Certificates link clicked!"); + Desktop.getDesktop().browse(new URI("http://www.google.com")); + } + + @FXML + void handleVaccinationLink(ActionEvent event) throws URISyntaxException, IOException { + // For now clicking the hyperlink will just open the browser + System.out.println("Vaccination link clicked!"); + Desktop.getDesktop().browse(new URI("http://www.google.com")); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PetCard)) { + return false; + } + + // state check + PetCard card = (PetCard) other; + return id.getText().equals(card.id.getText()) + && pet.equals(card.pet); + } +} diff --git a/src/main/java/seedu/address/ui/displaycards/SupplierCard.java b/src/main/java/seedu/address/ui/displaycards/SupplierCard.java new file mode 100644 index 00000000000..027a122464d --- /dev/null +++ b/src/main/java/seedu/address/ui/displaycards/SupplierCard.java @@ -0,0 +1,120 @@ +package seedu.address.ui.displaycards; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.logic.Logic; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; +import seedu.address.ui.UiPart; + +/** + * An UI component that displays information of a {@code Supplier}. + */ +public class SupplierCard extends UiPart { + + private static final String FXML = "SupplierListCard.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 Supplier supplier; + private final Logic logic; + private final int displayedIndex; + + + @FXML + private Label address; + + @FXML + private Label email; + + @FXML + private Label id; + + @FXML + private Label locatedCountry; + + @FXML + private Label name; + + @FXML + private ListView petListView; + + @FXML + private Label phone; + + /** + * Creates a {@code SupplierCard} with the given {@code Supplier} and index to display. + */ + public SupplierCard(Supplier supplier, int displayedIndex, Logic logic) { + super(FXML); + this.supplier = supplier; + this.displayedIndex = displayedIndex; + this.logic = logic; + fillSupplierCard(); + } + + /** + * Fills the relevant fields in the supplier card. + */ + public void fillSupplierCard() { + // Set the contact details + id.setText(displayedIndex + ". "); + name.setText(supplier.getName().fullName); + phone.setText(supplier.getPhone().value); + locatedCountry.setText(supplier.getLocation().location); + address.setText(supplier.getAddress().value); + email.setText(supplier.getEmail().value); + + // Set the buyer's orders in the list view + petListView.setItems(logic.getPetAsObservableListFromSupplier(supplier)); + petListView.setCellFactory(listView -> new SupplierPetsListViewCell()); + + /* supplier.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));*/ + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Pet} using a {@code PetCard}. + */ + private static class SupplierPetsListViewCell extends ListCell { + @Override + protected void updateItem(Pet pet, boolean empty) { + super.updateItem(pet, empty); + + if (empty || pet == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PetCard(pet, getIndex() + 1, PetCard.SHOULD_NOT_DISPLAY_SUPPLIER_NAME) + .getRoot()); + } + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SupplierCard)) { + return false; + } + + // state check + SupplierCard card = (SupplierCard) other; + return id.getText().equals(card.id.getText()) + && supplier.equals(card.supplier); + } +} diff --git a/src/main/java/seedu/address/ui/listpanels/MainListPanel.java b/src/main/java/seedu/address/ui/listpanels/MainListPanel.java new file mode 100644 index 00000000000..747f4e12fc3 --- /dev/null +++ b/src/main/java/seedu/address/ui/listpanels/MainListPanel.java @@ -0,0 +1,90 @@ +package seedu.address.ui.listpanels; + +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.logic.Logic; +import seedu.address.model.order.Order; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Deliverer; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; +import seedu.address.ui.UiPart; +import seedu.address.ui.displaycards.BuyerCard; +import seedu.address.ui.displaycards.DelivererCard; +import seedu.address.ui.displaycards.OrderCard; +import seedu.address.ui.displaycards.PetCard; +import seedu.address.ui.displaycards.SupplierCard; + +/** + * Panel containing the list of all contacts. + */ +public class MainListPanel extends UiPart { + private static final String FXML = "MainListPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(MainListPanel.class); + + @FXML + private ListView mainListView; + + + /** + * Creates a {@code MainListPanel} with the given {@code ObservableList}. + */ + public MainListPanel(ObservableList mainList, Logic logic) { + super(FXML); + mainListView.setItems(mainList); + mainListView.setCellFactory(listView -> new MainListViewCell(logic)); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Deliverer} using a {@code DelivererCard}. + */ + private static class MainListViewCell extends ListCell { + private final Logic logic; + + public MainListViewCell(Logic logic) { + this.logic = logic; + } + + @Override + protected void updateItem(Object object, boolean empty) { + super.updateItem(object, empty); + + if (empty || object == null) { + setGraphic(null); + setText(null); + } else { + if (object instanceof Buyer) { + Buyer buyer = (Buyer) object; + setGraphic(new BuyerCard(buyer, getIndex() + 1, logic).getRoot()); + } + if (object instanceof Supplier) { + Supplier supplier = (Supplier) object; + setGraphic(new SupplierCard(supplier, getIndex() + 1, logic).getRoot()); + } + if (object instanceof Deliverer) { + Deliverer deliverer = (Deliverer) object; + setGraphic(new DelivererCard(deliverer, getIndex() + 1, logic).getRoot()); + } + if (object instanceof Pet) { + Pet pet = (Pet) object; + setGraphic(new PetCard(pet, getIndex() + 1, PetCard.SHOULD_DISPLAY_SUPPLIER_NAME) + .getRoot()); + } + if (object instanceof Order) { + Order order = (Order) object; + setGraphic(new OrderCard(order, getIndex() + 1, OrderCard.SHOULD_DISPLAY_BUYER_NAME) + .getRoot()); + } + + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/popupwindow/AddCommandPopupWindow.java b/src/main/java/seedu/address/ui/popupwindow/AddCommandPopupWindow.java new file mode 100644 index 00000000000..c518c988b14 --- /dev/null +++ b/src/main/java/seedu/address/ui/popupwindow/AddCommandPopupWindow.java @@ -0,0 +1,202 @@ +package seedu.address.ui.popupwindow; + +import java.util.logging.Logger; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.geometry.Rectangle2D; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.StackPane; +import javafx.stage.Screen; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import seedu.address.MainApp; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.addcommands.AddCommandWithPopup; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.ui.ResultDisplay; +import seedu.address.ui.UiPart; +import seedu.address.ui.listpanels.MainListPanel; + +/** + * The pop-up window for adding a buyer with/without orders, + * or adding a supplier with/without pets. + */ +public class AddCommandPopupWindow extends UiPart { + + private static final Logger logger = LogsCenter.getLogger(AddCommandPopupWindow.class); + private static final String FXML = "AddCommandPopupWindow.fxml"; + private static final String ICON_APPLICATION = "/images/petCode.png"; + private final Stage stage; + private final Logic logic; + private final ResultDisplay resultDisplay; + private final Image image = new Image(MainApp.class.getResourceAsStream(ICON_APPLICATION)); + private PopUpPanel popUpPanel; + private MainListPanel mainListPanel; + private StackPane placeHolder; + + @FXML + private StackPane popupContentPlaceholder; + + @FXML + private Button saveButton; + + @FXML + private Label typeToBeAdded; + + /** + * Constructs a {@code AddCommandPopupWindow}. + * + * @param root Stage of the window. + * @param logic Logic for execution of the generated add command. + * @param typeToBeAdded Type of person to add. + * @param resultDisplay Result display window of the main window. + */ + public AddCommandPopupWindow(Stage root, Logic logic, String typeToBeAdded, ResultDisplay resultDisplay, + MainListPanel mainListPanel, StackPane placeholder) { + super(FXML, root); + this.stage = root; + this.logic = logic; + this.typeToBeAdded.setText(typeToBeAdded); + this.resultDisplay = resultDisplay; + this.stage.getIcons().add(image); + this.mainListPanel = mainListPanel; + this.placeHolder = placeholder; + setCloseWindowKey(KeyCode.ESCAPE); + setSaveButtonShortcut(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN)); + } + + public AddCommandPopupWindow(Logic logic, String typeToBeAdded, ResultDisplay resultDisplay, + MainListPanel mainListPanel, StackPane placeHolder) { + this(new Stage(), logic, typeToBeAdded, resultDisplay, mainListPanel, placeHolder); + } + + + + /** + * Fills the placeholder in the pop-up window with content based on the typo of person to be added. + * + * @param typeToBeAdded Type of person to be added. + */ + public void fillContentPlaceholder(String typeToBeAdded) { + typeToBeAdded = typeToBeAdded.trim().toUpperCase(); + popupContentPlaceholder.getChildren().clear(); + switch (typeToBeAdded) { + case AddCommandWithPopup.ADD_BUYER: + PopupPanelForBuyer popupPanelForBuyer = new PopupPanelForBuyer(); + popUpPanel = popupPanelForBuyer; + popupContentPlaceholder.getChildren().add(popupPanelForBuyer.getRoot()); + break; + case AddCommandWithPopup.ADD_SUPPLIER: + PopupPanelForSupplier popupPanelForSupplier = new PopupPanelForSupplier(stage); + popUpPanel = popupPanelForSupplier; + popupContentPlaceholder.getChildren().add(popupPanelForSupplier.getRoot()); + break; + default: + // Do nothing + } + } + + /** + * Converts the user input to a {@code Command} when the {@code saveButton} is pressed. + * + * @param event Action event. + * @throws CommandException If the execution of the generated command causes exception. + * @throws ParseException If the parsing of the user input into a command causes exception. + */ + @FXML + void exitWithSave(ActionEvent event) { + try { + if (!popUpPanel.checkAllPartsFilled()) { + return; + } + Command command = popUpPanel.generateCommand(); + CommandResult commandResult = logic.executeGivenCommand(command); + if (typeToBeAdded.equals(AddCommandWithPopup.ADD_BUYER)) { + logic.switchToBuyer(); + } else if (typeToBeAdded.equals(AddCommandWithPopup.ADD_SUPPLIER)) { + logic.switchToSupplier(); + } + refresh(); + logger.info("Result: " + commandResult.getFeedbackToUser()); + resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + close(); + } catch (CommandException | ParseException e) { + logger.info("Invalid command!"); + resultDisplay.setFeedbackToUser(e.getMessage()); + } + } + + /** + * Sets the keyboard shortcut for closing the pop-up window. + * + * @param keyCode A keyboard key. + */ + public void setCloseWindowKey(KeyCode keyCode) { + // Solution adapted from https://stackoverflow.com/questions/14357515/javafx-close-window-on-pressing-esc + stage.addEventHandler(KeyEvent.KEY_RELEASED, (KeyEvent event) -> { + if (event.getCode() == keyCode) { + close(); + } + }); + } + + /** + * Refreshes the current list. + */ + private void refresh() { + mainListPanel = new MainListPanel(logic.getFilteredCurrList(), logic); + placeHolder.getChildren().clear(); + placeHolder.getChildren().add(mainListPanel.getRoot()); + } + + /** + * Sets the keyboard shortcut for the save button. + * + * @param keyCombination A combination of keyboard keys. + */ + public void setSaveButtonShortcut(KeyCombination keyCombination) { + getRoot().addEventHandler(KeyEvent.KEY_PRESSED, event -> { + if (keyCombination.match(event)) { + saveButton.fire(); + event.consume(); + } + }); + } + + /** + * Shows the pop-up window. + */ + public void show() { + logger.fine("Showing add command pop-up window."); + Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds(); + + getRoot().initStyle(StageStyle.DECORATED); + getRoot().setWidth(screenBounds.getWidth() * 0.6); + getRoot().setHeight(screenBounds.getHeight() * 0.75); + getRoot().setX(screenBounds.getMinX()); + getRoot().setY(screenBounds.getMinY()); + getRoot().setMaxWidth(screenBounds.getWidth() * 2); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Closes the pop-up window. + */ + public void close() { + logger.fine("Add command pop-up window closed."); + stage.close(); + } + +} diff --git a/src/main/java/seedu/address/ui/popupwindow/PopUpPanel.java b/src/main/java/seedu/address/ui/popupwindow/PopUpPanel.java new file mode 100644 index 00000000000..61a9c8f565b --- /dev/null +++ b/src/main/java/seedu/address/ui/popupwindow/PopUpPanel.java @@ -0,0 +1,119 @@ +package seedu.address.ui.popupwindow; + +import javafx.scene.control.Button; +import javafx.scene.control.Control; +import javafx.scene.control.TextInputControl; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Region; +import seedu.address.logic.commands.Command; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.ui.UiPart; + +/** + * Represents a part of the pop-up window. + */ +public abstract class PopUpPanel extends UiPart { + + private static final String PROMPT_TEXT_STYLE = "-fx-prompt-text-fill: derive(-fx-control-inner-background, -30%);" + + " -fx-background-radius: 30px 30px 30px 30px;"; + private static final String ERROR_FOCUS_STYLE = "-fx-focus-color: red;\n" + + "-fx-faint-focus-color: #ff000022;" + PROMPT_TEXT_STYLE; + + public PopUpPanel(String fxml) { + super(fxml); + } + + /** + * Generates a {@code Command} from all the user inputs in the panel. + * + * @return A {@code Command}, a {@code AddCommand} to be precise. + * @throws ParseException When parsing of the user inputs causes exception. + */ + public abstract Command generateCommand() throws ParseException; + + /** + * Checks whether all fields that require user inputs are filled. + * + * @return True if all compulsory fields are filled, false otherwise. + */ + public abstract boolean checkAllPartsFilled(); + + /** + * Changes focus from {@code currentField} to {@code nextField} with the ENTER key in the keyboard. + * + * @param currentField Current field in focus. + * @param nextField Target field to change focus to. + */ + private void goToNextFieldWithEnter(Control currentField, Control nextField) { + // Solution below adapted from http://www.shorturl.at/aHLX7 + currentField.setOnKeyPressed(event -> { + if (event.getCode().equals(KeyCode.ENTER)) { + nextField.requestFocus(); + } + }); + } + + /** + * Generate a sequence of fields in focus with the ENTER key, + * where the first field in {@code fields} will be the initial field in focus without the need to press ENTER, + * while the last field in {@code fields} will be the last field in focus by pressing ENTER. + * + * @param fields Any number of {@code Control} objects. + */ + public void generateInputSequence(Control... fields) { + Control currentField = null; + for (Control nextField : fields) { + if (currentField != null) { + goToNextFieldWithEnter(currentField, nextField); + } + currentField = nextField; + } + } + + /** + * Checks all fields provided are filled with inputs. + * + * @param textInputFields Fields related to {@code TextInputControl}. + * @return True if all fields are filled, false otherwise. + */ + public boolean checkGivenFieldsAllFilled(TextInputControl... textInputFields) { + for (TextInputControl textInputField : textInputFields) { + if (textInputField.getText().isEmpty() || textInputField.getText().startsWith(" ")) { + textInputField.requestFocus(); + textInputField.setStyle(ERROR_FOCUS_STYLE); + return false; + } + } + return true; + } + + /** + * Sets the style of all the given fields to the style + * that displays the prompt text even when the field is in focus. + * + * @param textInputFields Any number of {@code TextInputControl}. + */ + public void setPromptTextStyle(TextInputControl... textInputFields) { + for (TextInputControl textInputField : textInputFields) { + textInputField.setStyle(PROMPT_TEXT_STYLE); + } + } + + /** + * Generate a keyboard shortcut to the given button. + * + * @param button A button. + * @param keyCombination A combination of keyboard keys. + */ + public void generateButtonShortcut(Button button, KeyCombination keyCombination) { + getRoot().addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent event) -> { + if (event.getTarget() instanceof TextInputControl && keyCombination.match(event)) { + button.fire(); + event.consume(); + } + }); + } + +} diff --git a/src/main/java/seedu/address/ui/popupwindow/PopupPanelForBuyer.java b/src/main/java/seedu/address/ui/popupwindow/PopupPanelForBuyer.java new file mode 100644 index 00000000000..e0cb61c7e77 --- /dev/null +++ b/src/main/java/seedu/address/ui/popupwindow/PopupPanelForBuyer.java @@ -0,0 +1,161 @@ +package seedu.address.ui.popupwindow; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.layout.GridPane; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.addcommands.AddBuyerCommand; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.Order; +import seedu.address.model.person.Address; +import seedu.address.model.person.Buyer; +import seedu.address.model.person.Email; +import seedu.address.model.person.Location; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; + +/** + * A panel for entering buyer information, which can be used to fill the placeholder in the pop-up window. + * It can contain any number of {@code PopupPanelForOrder}. + */ +public class PopupPanelForBuyer extends PopUpPanel { + + private static final String FXML = "PopupPanelForBuyer.fxml"; + private final List orderComponents; + + @FXML + private Button addComponentButton; + + @FXML + private TextField addressField; + + @FXML + private GridPane componentPlaceholder; + + @FXML + private TextField countryField; + + @FXML + private Button deleteComponentButton; + @FXML + private TextField emailField; + + @FXML + private TextField nameField; + + @FXML + private TextField phoneField; + + + @FXML + private ScrollPane scrollPane; + + /** + * Constructs a {@code PopupPanelForBuyer} by setting input sequence, prompt text style and keyboard shortcut. + */ + public PopupPanelForBuyer() { + super(FXML); + orderComponents = new ArrayList<>(); + nameField.requestFocus(); + generateInputSequence(nameField, phoneField, emailField, countryField, addressField, addComponentButton); + setPromptTextStyle(nameField, phoneField, emailField, countryField, addressField); + generateButtonShortcut(addComponentButton, new KeyCodeCombination(KeyCode.A, KeyCombination.CONTROL_DOWN)); + generateButtonShortcut(deleteComponentButton, new KeyCodeCombination(KeyCode.D, KeyCombination.CONTROL_DOWN)); + } + + /** + * Adds an order component to the temporary storage list and the displayed grid pane + * when the {@code addComponentButton} is pressed. + * + * @param event An action event. + */ + @FXML + public void addOrderComponent(ActionEvent event) { + PopupPanelForOrder orderComponent = new PopupPanelForOrder(); + orderComponents.add(orderComponent); + int numOfComponents = orderComponents.size(); + componentPlaceholder.addRow(numOfComponents - 1, orderComponent.getRoot()); + } + + /** + * Deletes an order component from the temporary storage list and the displayed grid pane + * when the {@code deleteComponentButton} is pressed. + * + * @param event An action event. + */ + @FXML + void deleteOrderComponent(ActionEvent event) { + // Solution adapted from + // https://stackoverflow.com/questions/23002532/javafx-2-how-do-i-delete-a-row-or-column-in-gridpane + int numOfComponents = orderComponents.size(); + if (numOfComponents > 0) { + orderComponents.remove(numOfComponents - 1); + componentPlaceholder.getChildren().removeIf(node -> GridPane.getRowIndex(node) == numOfComponents - 1); + } + } + + @Override + public Command generateCommand() throws ParseException { + Buyer buyer = generateBuyer(); + List orders = generateOrders(buyer); + buyer.addOrders(orders.stream().map(Order::getId).collect(Collectors.toList()));; + return new AddBuyerCommand(buyer, orders); + } + + /** + * Generates a {@code Buyer} from user inputs. + * + * @return A {@code Buyer}. + * @throws ParseException When the user inputs cannot be parsed. + */ + public Buyer generateBuyer() throws ParseException { + Name name = ParserUtil.parseName(nameField.getText()); + Phone phone = ParserUtil.parsePhone(phoneField.getText()); + Email email = ParserUtil.parseEmail(emailField.getText()); + Address address = ParserUtil.parseAddress(addressField.getText()); + Location location = new Location(countryField.getText()); + return new Buyer(name, phone, email, address, location, null); + } + + /** + * Generates a list of {@code Order} from the user inputs. + * + * @param buyer Buyer of the orders. + * @return A list of {@code Order}. + * @throws ParseException When the user inputs cannot be parsed. + */ + public List generateOrders(Buyer buyer) throws ParseException { + List orders = new ArrayList<>(); + for (PopupPanelForOrder order : orderComponents) { + orders.add(order.generateOrder(buyer)); + } + return orders; + } + + @Override + public boolean checkAllPartsFilled() { + boolean contactFilled = checkGivenFieldsAllFilled(nameField, phoneField, + emailField, countryField, addressField); + if (!contactFilled) { + return false; + } + for (PopupPanelForOrder orderComponent : orderComponents) { + if (!orderComponent.checkAllPartsFilled()) { + return false; + } + } + return true; + } + +} diff --git a/src/main/java/seedu/address/ui/popupwindow/PopupPanelForOrder.java b/src/main/java/seedu/address/ui/popupwindow/PopupPanelForOrder.java new file mode 100644 index 00000000000..8cdd85e6297 --- /dev/null +++ b/src/main/java/seedu/address/ui/popupwindow/PopupPanelForOrder.java @@ -0,0 +1,95 @@ +package seedu.address.ui.popupwindow; + +import java.time.LocalDate; + +import javafx.fxml.FXML; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import seedu.address.logic.commands.Command; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.AdditionalRequests; +import seedu.address.model.order.Order; +import seedu.address.model.order.OrderStatus; +import seedu.address.model.order.Price; +import seedu.address.model.order.PriceRange; +import seedu.address.model.order.Request; +import seedu.address.model.person.Buyer; +import seedu.address.model.pet.Age; +import seedu.address.model.pet.Color; +import seedu.address.model.pet.ColorPattern; +import seedu.address.model.pet.Species; + +/** + * A panel for entering order information, which can be part of the {@code PopupPanelForBuyer}. + */ +public class PopupPanelForOrder extends PopUpPanel { + + private static final String FXML = "PopupPanelForOrder.fxml"; + + @FXML + private TextArea additionalRequestsField; + @FXML + private TextField ageField; + + @FXML + private TextField byDateField; + + @FXML + private TextField colorField; + + @FXML + private TextField colorPatternField; + + @FXML + private TextField priceRangeField; + + @FXML + private TextField speciesField; + + /** + * Constructs a {@code PopupPanelForOrder} by setting input sequence and prompt text style. + */ + public PopupPanelForOrder() { + super(FXML); + speciesField.requestFocus(); + generateInputSequence(speciesField, colorField, colorPatternField, + ageField, priceRangeField, byDateField, additionalRequestsField); + setPromptTextStyle(speciesField, colorField, colorPatternField, + ageField, priceRangeField, byDateField); + } + + @Override + public Command generateCommand() { + // TODO: modify AddOrderCommand + return null; + } + + /** + * Generates an {@code Order} from user inputs. + * @param buyer Buyer of the order. + * @return An {@code Order}. + * @throws ParseException When user inputs cannot be parsed. + */ + public Order generateOrder(Buyer buyer) throws ParseException { + Species species = ParserUtil.parseSpecies(speciesField.getText()); + Color color = ParserUtil.parseColor(colorField.getText()); + ColorPattern colorPattern = ParserUtil.parseColorPattern(colorPatternField.getText()); + Age age = ParserUtil.parseAge(ageField.getText()); + PriceRange priceRange = ParserUtil.parsePriceRange(priceRangeField.getText()); + LocalDate localDate = ParserUtil.parseDate(byDateField.getText()); + String description = additionalRequestsField.getText().replaceAll("\n", System.lineSeparator()); + AdditionalRequests additionalRequests = new AdditionalRequests(description); + Request request = new Request(age, color, colorPattern, species); + Price price = new Price(-1); + return new Order(buyer, priceRange, request, additionalRequests, localDate, price, OrderStatus.PENDING); + } + + @Override + public boolean checkAllPartsFilled() { + boolean allPartsFilled = checkGivenFieldsAllFilled(speciesField, colorField, colorPatternField, + ageField, priceRangeField, byDateField); + return allPartsFilled; + } + +} diff --git a/src/main/java/seedu/address/ui/popupwindow/PopupPanelForPet.java b/src/main/java/seedu/address/ui/popupwindow/PopupPanelForPet.java new file mode 100644 index 00000000000..e9265a63049 --- /dev/null +++ b/src/main/java/seedu/address/ui/popupwindow/PopupPanelForPet.java @@ -0,0 +1,113 @@ +package seedu.address.ui.popupwindow; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.TextField; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import seedu.address.logic.commands.Command; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.order.Price; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Color; +import seedu.address.model.pet.ColorPattern; +import seedu.address.model.pet.DateOfBirth; +import seedu.address.model.pet.Height; +import seedu.address.model.pet.Pet; +import seedu.address.model.pet.PetCertificate; +import seedu.address.model.pet.PetName; +import seedu.address.model.pet.Species; +import seedu.address.model.pet.VaccinationStatus; +import seedu.address.model.pet.Weight; + +/** + * A panel for entering pet information, which can be part of the {@code PopupPanelForSupplier}. + */ +public class PopupPanelForPet extends PopUpPanel { + + private static final String FXML = "PopupPanelForPet.fxml"; + private final Stage stage; + + @FXML + private TextField colorField; + + @FXML + private TextField colorPatternField; + + @FXML + private TextField dateOfBirthField; + + @FXML + private TextField heightField; + + @FXML + private TextField petNameField; + + @FXML + private TextField priceField; + + @FXML + private TextField speciesField; + + @FXML + private TextField weightField; + + /** + * Constructs a {@code PopupPanelForPet} by setting input sequence and prompt text style. + */ + public PopupPanelForPet(Stage stage) { + super(FXML); + this.stage = stage; + petNameField.requestFocus(); + generateInputSequence(petNameField, speciesField, heightField, weightField, + dateOfBirthField, colorField, colorPatternField, priceField); + setPromptTextStyle(petNameField, speciesField, heightField, weightField, + dateOfBirthField, colorField, colorPatternField, priceField); + } + + @FXML + void openFileExplorer(ActionEvent event) { + FileChooser fileChooser = new FileChooser(); + File file = fileChooser.showOpenDialog(stage); + } + + @Override + public Command generateCommand() { + return null; + } + + /** + * Generates a {@code Pet} from user inputs. + * @param supplier Supplier of the order. + * @return A {@code Pet}. + * @throws ParseException When user inputs cannot be parsed. + */ + public Pet generatePet(Supplier supplier) throws ParseException { + PetName name = ParserUtil.parsePetName(petNameField.getText()); + Species species = ParserUtil.parseSpecies(speciesField.getText()); + Height height = ParserUtil.parseHeight(heightField.getText()); + Weight weight = ParserUtil.parseWeight(weightField.getText()); + DateOfBirth dateOfBirth = ParserUtil.parseDateOfBirth(dateOfBirthField.getText()); + Color color = ParserUtil.parseColor(colorField.getText()); + ColorPattern colorPattern = ParserUtil.parseColorPattern(colorPatternField.getText()); + Price price = ParserUtil.parsePrice(priceField.getText()); + + // Since the current version does not allow the users to upload documents, dummy instantiations are used + VaccinationStatus vaccinationStatus = new VaccinationStatus(false); + Set certificates = new HashSet<>(); + return new Pet(name, supplier, color, colorPattern, dateOfBirth, species, + weight, height, vaccinationStatus, price, certificates); + } + + @Override + public boolean checkAllPartsFilled() { + boolean allPartsFilled = checkGivenFieldsAllFilled(petNameField, speciesField, heightField, weightField, + dateOfBirthField, colorField, colorPatternField, priceField); + return allPartsFilled; + } +} diff --git a/src/main/java/seedu/address/ui/popupwindow/PopupPanelForSupplier.java b/src/main/java/seedu/address/ui/popupwindow/PopupPanelForSupplier.java new file mode 100644 index 00000000000..74b4fae25cb --- /dev/null +++ b/src/main/java/seedu/address/ui/popupwindow/PopupPanelForSupplier.java @@ -0,0 +1,158 @@ +package seedu.address.ui.popupwindow; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.layout.GridPane; +import javafx.stage.Stage; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.addcommands.AddSupplierCommand; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Location; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.Supplier; +import seedu.address.model.pet.Pet; + +/** + * A panel for entering supplier information, which can be used to fill the placeholder in the pop-up window. + * It can contain any number of {@code PopupPanelForPet}. + */ +public class PopupPanelForSupplier extends PopUpPanel { + + private static final String FXML = "PopupPanelForSupplier.fxml"; + private final Stage stage; + private final List petComponents; + + @FXML + private Button addComponentButton; + + @FXML + private TextField addressField; + + @FXML + private GridPane componentPlaceholder; + + @FXML + private TextField countryField; + + @FXML + private Button deleteComponentButton; + + @FXML + private TextField emailField; + + @FXML + private TextField nameField; + + @FXML + private TextField phoneField; + + /** + * Constructs a {@code PopupPanelForSupplier} by setting input sequence, prompt text style and keyboard shortcut. + */ + public PopupPanelForSupplier(Stage stage) { + super(FXML); + this.stage = stage; + petComponents = new ArrayList<>(); + generateInputSequence(nameField, phoneField, emailField, countryField, addressField, addComponentButton); + setPromptTextStyle(nameField, phoneField, emailField, countryField, addressField); + generateButtonShortcut(addComponentButton, new KeyCodeCombination(KeyCode.A, KeyCombination.CONTROL_DOWN)); + generateButtonShortcut(deleteComponentButton, new KeyCodeCombination(KeyCode.D, KeyCombination.CONTROL_DOWN)); + } + + /** + * Adds a pet component to the temporary storage list and the displayed grid pane + * when the {@code addComponentButton} is pressed. + * + * @param event An action event. + */ + @FXML + public void addPetComponent(ActionEvent event) { + PopupPanelForPet petComponent = new PopupPanelForPet(stage); + petComponents.add(petComponent); + int numOfComponents = petComponents.size(); + componentPlaceholder.addRow(numOfComponents - 1, petComponent.getRoot()); + } + + /** + * Deletes a pet component from the temporary storage list and the displayed grid pane + * when the {@code deleteComponentButton} is pressed. + * + * @param event An action event. + */ + @FXML + void deletePetComponent(ActionEvent event) { + int numOfComponents = petComponents.size(); + if (numOfComponents > 0) { + petComponents.remove(numOfComponents - 1); + componentPlaceholder.getChildren().removeIf(node -> GridPane.getRowIndex(node) == numOfComponents - 1); + } + } + + @Override + public Command generateCommand() throws ParseException { + Supplier supplier = generateSupplier(); + List pets = generatePets(supplier); + supplier.addPets(pets.stream().map(Pet::getId).collect(Collectors.toList())); + return new AddSupplierCommand(supplier, pets); + } + + /** + * Generates a {@code Supplier} from user inputs. + * + * @return A {@code Supplier}. + * @throws ParseException When the user inputs cannot be parsed. + */ + public Supplier generateSupplier() throws ParseException { + Name name = ParserUtil.parseName(nameField.getText()); + Phone phone = ParserUtil.parsePhone(phoneField.getText()); + Email email = ParserUtil.parseEmail(emailField.getText()); + Address address = ParserUtil.parseAddress(addressField.getText()); + Location location = new Location(countryField.getText()); + return new Supplier(name, phone, email, address, location, null); + } + + /** + * Generates a list of {@code Pet} from the user inputs. + * + * @param supplier Supplier of the pets. + * @return A list of {@code Order}. + * @throws ParseException When the user inputs cannot be parsed. + */ + public List generatePets(Supplier supplier) throws ParseException { + List pets = new ArrayList<>(); + for (PopupPanelForPet petComponent : petComponents) { + pets.add(petComponent.generatePet(supplier)); + } + return pets; + } + + @Override + public boolean checkAllPartsFilled() { + boolean contactFilled = checkGivenFieldsAllFilled(nameField, phoneField, + emailField, countryField, addressField); + if (!contactFilled) { + return false; + } + for (PopupPanelForPet petComponent : petComponents) { + if (!petComponent.checkAllPartsFilled()) { + return false; + } + } + return true; + } + + +} diff --git a/src/main/resources/images/dummy_pet_image.png b/src/main/resources/images/dummy_pet_image.png new file mode 100644 index 00000000000..a256af8d56c Binary files /dev/null and b/src/main/resources/images/dummy_pet_image.png differ diff --git a/src/main/resources/images/petCode.png b/src/main/resources/images/petCode.png new file mode 100644 index 00000000000..dc122068e8c Binary files /dev/null and b/src/main/resources/images/petCode.png differ diff --git a/src/main/resources/view/AddCommandPopupWindow.fxml b/src/main/resources/view/AddCommandPopupWindow.fxml new file mode 100644 index 00000000000..ec8274ef183 --- /dev/null +++ b/src/main/resources/view/AddCommandPopupWindow.fxml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PopupPanelForOrder.fxml b/src/main/resources/view/PopupPanelForOrder.fxml new file mode 100644 index 00000000000..4003302b685 --- /dev/null +++ b/src/main/resources/view/PopupPanelForOrder.fxml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +