` 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)
### Storage component
**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/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).
+* can save both MyCrm data and user preference data in json format, and read them back into corresponding objects.
+* inherits from both `MyCrmStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed).
* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`)
### Common classes
-Classes used by multiple components are in the `seedu.addressbook.commons` package.
+Classes used by multiple components are in the `seedu.mycrm.commons` package.
--------------------------------------------------------------------------------------------------------------------
@@ -154,90 +205,614 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+* [Adding a contact](#adding-a-contact)
+* [Editing a contact](#editing-a-contact)
+* [Deleting a contact](#deleting-a-contact)
+* [Finding a contact](#finding-a-contact)
+* [Hiding a contact](#hiding-a-contact)
+* [Undoing Hiding a contact](#undoing-hiding-a-contact)
+* [Listing contacts](#listing-contacts)
+* [Adding a template](#adding-a-template)
+* [Editing a template](#editing-a-template)
+* [Deleting a template](#deleting-a-template)
+* [Finding a template](#finding-a-template)
+* [Constructing an email](#constructing-an-email)
+* [Adding a product](#adding-a-product)
+* [Editing a product](#editing-a-product)
+* [StateManager](#statemanager)
+* [Adding a job](#adding-a-job)
+* [Editing a job](#editing-a-job)
+* [Printing a monthly job report](#printing-a-monthly-job-report)
-#### Proposed Implementation
+### Adding a Contact
-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:
+#### Implementation
-* `VersionedAddressBook#commit()` — Saves the current address book state in its history.
-* `VersionedAddressBook#undo()` — Restores the previous address book state from its history.
-* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history.
+The **Adding a Contact** mechanism is facilitated by `MyCRM`. This Contact created is stored internally using
+`UniqueContactList` inside the `MyCrm` object.
+Additionally, `addContact` allows to have only partially info of a client with consideration of privacy. Commands
+such as `AddContact n/xxx e/xxx` `addContact n/xxx c/xxx` are all acceptable.
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+#### Usage
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+The activity diagram below illustrates how the events of `addContact` command behave when executed by a user:
-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.
+[![](images/contact/AddContactActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/AddContactActivityDiagram.png)
-![UndoRedoState0](images/UndoRedoState0.png)
+Given below is an example usage scenario and how the **Adding a Contact** mechanism behaves at each step.
-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.
+[![](images/contact/AddContactParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/AddContactParseSequenceDiagram.png)
-![UndoRedoState1](images/UndoRedoState1.png)
+Within `AddContactCommandParser#parse`, `ParserUtil#parseName` will be called to create a name using
+"Sans", `ParserUtil#parsePhone` to create a phone using "83921823", `ParserUtil#parseEmail` to
+create an email using "Sans@gmail.com", `ParserUtil#parseAddress` to create an address using "Maxwell...".
+Then create a contact using the new name, phone, email and address.
-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`.
+Note that `Phone`, `Email`, `Address`, are optional, but at least one of these 3 fields
+must exist.
-![UndoRedoState2](images/UndoRedoState2.png)
+[![](images/contact/AddContactSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/AddContactSequenceDiagram.png)
-:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.
+Note: Please refer to [StateManager](#statemanager) for more details on why the command result of the AddContactCommand is intercepted.
-
+### Editing a Contact
-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.
+#### Implementation
-![UndoRedoState3](images/UndoRedoState3.png)
+The **Editing a Contact** mechanism is facilitated by `MyCRM`. This mechanism reads and modifies a target contact
+object from `UniqueContactList` inside the `MyCRM` object.
-:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather
-than attempting to perform the undo.
+#### Usage
-
+The activity diagram below illustrates how the events of `editContact` command behave when executed by a user:
-The following sequence diagram shows how the undo operation works:
+[![](images/contact/EditContactActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/EditContactActivityDiagram.png)
-![UndoSequenceDiagram](images/UndoSequenceDiagram.png)
+Given below is an example usage scenario and how the **Editing a Contact** mechanism behaves at each step.
-:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+[![](images/contact/EditContactParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/EditContactParseSequenceDiagram.png)
-
+Within `EditContactCommandParser#parse`,
+- `Index` must be is valid (within the range of contactList).
+- `EditContactDescriptor` will only get the values of `Name`, `Phone`, `Email`, `Address`, and `Tags`
+if their respective prefixes are present.
+- `isHidden` is will not be handled by `EditContactDescrptior`, it will be updated in `createEditedContact`.
-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.
+`EditContactCommandParser#parse` will call `ArgumentMultimap#getPreamble` to get the target contact's index and
+`ArgumentMultimap#getValue` to extract `Name`, `Phone`, `Email`, `Address`: "Frisks", "88888888", "Frisks@gmail.com"
+and "Jurong West" from the command string respectively.
-: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.
+[![](images/contact/EditContactSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/EditContactSequenceDiagram.png)
-
+### Deleting a Contact
+
+#### Implementation
+
+The **Deleting a Contact** mechanism is facilitated by `MyCRM`. This mechanism reads and deletes a target contact
+object from `UniqueContactList` inside the `MyCRM` object.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `deleteContact` command behave when executed by a user:
+
+[![](images/contact/DeleteContactActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/DeleteContactActivityDiagram.png)
+
+Given below is an example usage scenario and how the **Deleting a Contact** mechanism behaves at each step.
+
+[![](images/contact/DeleteContactParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/DeleteContactParseSequenceDiagram.png)
+
+Within `DeleteContactCommandParser#parse`,
+- `Index` must be is valid (within the range of contactList).
+- The contact specified to be deleted must have no jobs linked, otherwise error message will be displayed in UI panel.
+
+`DeleteContactCommandParser#parse` will call `ParserUtil#parseIndex` to get the target contact's index to delete it.
+
+[![](images/contact/DeleteContactSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/DeleteContactSequenceDiagram.png)
+
+### Finding a Contact
+
+#### Implementation
+
+The **Finding a Contact** mechanism is facilitated by `MyCRM`. This mechanism finds specific list of contact
+object from `UniqueContactList` inside the `MyCRM` object with certain keywords provided.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `findContact` command behave when executed by a user:
+
+[![](images/contact/FindContactActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/FindContactActivityDiagram.png)
+
+Given below is an example usage scenario and how the **Finding a Contact** mechanism behaves at each step.
+
+[![](images/contact/FindContactParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/FindContactParseSequenceDiagram.png)
+
+
+Within `FindContactCommandParser#parse`,
+- `Keywords` must be presented. (At least one trim of String)
+
+`FindContactCommandParser#parse` will call `String#trim` and `String#split` to get list of keywords
+in order for MyCRM to find corresponding contacts with these keywords as predicate.
+
+[![](images/contact/FindContactSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/FindContactSequenceDiagram.png)
+
+### Hiding a Contact
+
+#### Implementation
+
+The **Hiding a Contact** mechanism is facilitated by the `MyCRM`. It hides a specific contact
+which is visible only when user types the command `listContact -a`. Hidden contact will be tagged as `Hidden`.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `hideContact` command behave when executed by a user:
+
+[![](images/contact/HideContactActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/HideContactActivityDiagram.png)
+
+Given below is an example usage scenario and how the **Hiding a Contact** mechanism behaves at each step.
+
+[![](images/contact/HideContactParserSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/HideContactParseSequenceDiagram.png)
+
+Within `HideContactCommandParser#parse`,
+- `Index` must be is valid (within the range of contactList).
+
+`HideContactCommandParser#parse` will call `ParserUtil#parseIndex` to get the target contact's index to hide it.
+
+[![](images/contact/HideContactSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/HideContactSequenceDiagram.png)
+
+### Undoing Hiding a Contact
+
+#### Implementation
+
+The **Undoing Hiding a Contact** mechanism is facilitated by the `MyCRM`. It will unhide a hidden contact in list. Users are
+required to type in command `listContact -a` in order to see **hidden** contacts.
+
+Implementation and usage details for **Undoing Hiding a Contact** are similar to [Hiding a Contact](#hiding-a-contact) design
+pattern. Can refer to `hideContact` command implementation details.
+#### Usage
+
+The activity diagram below illustrates how the events of `undoHideContact` command behave when executed by a user:
+
+[![](images/contact/UndoHideContactActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/UndoHideContactActivityDiagram.png)
+
+Given below is an example usage scenario and how the **Undoing Hiding a Contact** mechanism behaves at each step.
+
+[![](images/contact/UndoHideContactParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/UndoHideContactParseSequenceDiagram.png)
+
+Within `UndoHideContactCommandParser#parse`,
+- `listContact -a` must first be typed in to see hidden contacts.
+- `Index` must be is valid (within the range of contactList).
+
+`UndoHideContactCommandParser#parse` will call `ParserUtil#parseIndex`
+
+[![](images/contact/UndoHideContactSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/UndoHideContactSequenceDiagram.png)
+
+### Listing Contacts
+
+#### Implementation
+
+The **Listing a Contact** mechanism is facilitated by `MyCRM`. This mechanism lists all unhidden Contact
+object from `UniqueContactList` inside the `MyCRM` object by default. If `listContact -a` is invoked,
+`MyCRM` will list all contacts including not hidden ones.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `listContact` command behave when executed by a user:
+
+[![](images/contact/ListContactActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/ListContactActivityDiagram.png)
+
+Given below is an example usage scenario and how the **Listing a Contact** mechanism behaves at each step.
+
+[![](images/contact/ListContactParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/ListContactParseSequenceDiagram.png)
+
+Within `ListContactCommandParser#parse`,
+- `Keywords` is optional but if provided, it can only be **"-a"**.
+- If correct keyword is presented, contact list will show all contacts including hidden contacts.
+If not, by default `listContact` will only show not hidden contacts in contact list.
+
+`ListcontactCommandParser#parse` will call `String#trim` to get specific keyword.
+
+[![](images/contact/ListContactSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/contact/ListContactSequenceDiagram.png)
+
+### Adding a Template
+
+#### Implementation
+
+The **Adding a Template** mechanism is facilitated by `MyCRM`. This template created is stored internally using
+`UniqueTemplateList` inside the `MyCRM` object.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `addTemplate` command behave when executed by user:
+
+[![](images/mail/AddTemplateActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/AddTemplateActivityDiagram.png)
+
+Given below is an example usage scenario and how the Adding a Template mechanism behaves at each step.
+
+[![](images/mail/AddTemplateParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/AddTemplateParseSequenceDiagram.png)
+
+**Parse user input**
+
+Within `AddTemplateCommandParser#parse`, `ParserUtil#parseSubject` will be called to create a subject using
+"Completed", `ParserUtil#parseBody` to create a body using "Dear customer..." and create a template using the new
+subject and body.
+
+[![](images/mail/AddTemplateSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/AddTemplateSequenceDiagram.png)
+
+Additionally, before adding the new template into `Model`, `Template t` will be checked if a similar copy exist
+within `Model`. The conditions required is:
+
+* If both templates have the DO NOT same `Subject` content i.e. there is an existing template with subject "Completed".
+
+#### Design Considerations
+
+**Aspect: Unique Template**
+
+* Current choice: Unique by Subject
+ * Pros: Easy to understand and differentiate templates
+ * Cons: Does not allow for different variations of general / common email headers i.e. "Completed" subject header
+ cannot be reused with different body content based on scenario
+* Alternative: Unique by Subject and Body
+ * Pros: Enables different variations of general / common email headers
+ * Cons: May not be user-friendly as it may be hard to differentiate templates as some may be too similar at first
+ glance. Such as, minor typos, copy and paste with a couple of different words. Higher risk of confusion.
+
+### Editing a Template
+
+#### Implementation
+
+The **Editing a Template** mechanism is facilitated by `MyCRM`. This mechanism reads and modifies a target template
+object from `UniqueTemplateList` inside the `MyCRM` object.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `editTemplate` command behave when executed by user:
+
+[![](images/mail/EditTemplateActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/EditTemplateActivityDiagram.png)
+
+Given below is an example usage scenario and how the Editing a Template mechanism behaves at each step.
+
+[![](images/mail/EditTemplateParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/EditTemplateParseSequenceDiagram.png)
+
+**Parse user input**
+
+Within `EditTemplateCommandParser#parse`,
+- `Index` must be is valid (within the range of templates).
+- `EditTemplateDescriptor` will only get the values of `Subject` and `Body` if their respective prefixes are present.
+
+`EditTemplateCommandParser#parse` will call `ArgumentMultimap#getPreamble` to get the specified template index and
+`ArgumentMultimap#getValue` to extract both `Subject` and `Body`: "Completed" and "Order Completed!" from the
+command string respectively.
+
+[![](images/mail/EditTemplateSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/EditTemplateSequenceDiagram.png)
+
+### Deleting a Template
+
+#### Implementation
+
+The **Deleting a Template** mechanism is facilitated by `MyCRM`. This template removes a target template object from
+`UniqueTemplateList` inside the `MyCRM` object.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `deleteTemplate` command behave when executed by user:
+
+[![](images/mail/DeleteTemplateActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/DeleteTemplateActivityDiagram.png)
+
+Given below is an example usage scenario and how the Deleting a Template mechanism behaves at each step.
+
+[![](images/mail/DeleteTemplateParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/DeleteTemplateParseSequenceDiagram.png)
+
+**Parse user input**
+
+Within `DeleteTemplateCommandParser#parse`,
+- `Index` must be is valid (within the range of templates) and at least one field to be edited, for the mechanism to
+ execute successfully.
+- `ParserUtil#parseIndex` will be called to extract the index of the specified template to delete.
+
+[![](images/mail/DeleteTemplateSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/DeleteTemplateSequenceDiagram.png)
+
+### Finding a Template
+
+#### Implementation
+
+The **Finding a Template** mechanism is facilitated by `MyCRM`. This mechanism finds specific list of template object
+from `UniqueTemplateList` inside the `MyCRM` object with certain keywords provided.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `findTemplate` command behave when executed by a user:
+
+[![](images/mail/FindTemplateActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/FindTemplateActivityDiagram.png)
+
+Given below is an example usage scenario and how the Finding a Template mechanism behaves at each step.
+
+[![](images/mail/FindTemplateParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/FindTemplateParseSequenceDiagram.png)
+
+**Parse user input**
+
+Within `FindTemplateCommandParser#parse`,
+- At least one keyword must be presented.
+- Keyword specified must be whole word.
+
+`FindTemplateCommandParser#parse` will call `String#trim` and `String#split` to get list of keywords
+in order for MyCRM to find corresponding templates with these keywords as predicate.
+
+[![](images/mail/FindTemplateSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/FindTemplateSequenceDiagram.png)
+
+### Constructing an Email
+
+#### Implementation
+
+The **Constructing an Email** mechanism is facilitated by `MyCRM`. This email is constructed based of the information
+from a specified job and template from `UniqueJobList` and `UniqueTemplateList` inside the `MyCRM` object. A
+mailto URL will be generated, sending users to their default mailing application with details of the job and
+template.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `mail` command behave when executed by user:
-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.
+[![](images/mail/MailActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/MailActivityDiagram.png)
-![UndoRedoState4](images/UndoRedoState4.png)
+Given below is an example usage scenario and how the Constructing an Email mechanism behaves at each step.
-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.
+[![](images/mail/MailParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/MailParseSequenceDiagram.png)
-![UndoRedoState5](images/UndoRedoState5.png)
+**Parse user input**
-The following activity diagram summarizes what happens when a user executes a new command:
+Within `MailCommandParser#parse`,
+- `JobIndex` must be is valid (within the range of job).
+- `TemplateIndex` must be is valid (within the range of templates).
+- `ParserUtil#parseIndex` will be called to extract both the index of the specified job and template to mail.
-
+[![](images/mail/MailSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/MailSequenceDiagram.png)
-#### Design considerations:
+An additional feature of constructing an email is the generation of a mailto URL, allowing for users to transfer
+their job and template details to the user's default mailing application.
-**Aspect: How undo & redo executes:**
+Given below is an example of the **generation of a mailto URL**:
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+[![](images/mail/MailUrlSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/mail/MailUrlSequenceDiagram.png)
-* **Alternative 2:** Individual command knows how to undo/redo by
- itself.
- * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
- * Cons: We must ensure that the implementation of each individual command are correct.
+After the URL is generated, the URL string is passed to a JavaFX Hyperlink object that when clicked, will
+execute the URL path.
-_{more aspects and alternatives to be added}_
+#### Design Considerations
-### \[Proposed\] Data archiving
+**Aspect: Mailing Extension**
-_{Explain here how the data archiving feature will be implemented}_
+* Current choice: mailto URL
+ * Pros: Easy to implement, is not limited to any specific email application, does not require user's email account.
+ * Cons: Does not allow for direct (immediate) sending of mail to client email addresses.
+* Alternative: Embedding Oauth with email service
+ * Pros: Enables direct and quick sending of email directly from MyCRM application
+ * Cons: Higher complexity in implementation with JavaFX, requires internet access to save drafts of emails,
+ requires the storage and security assurance of user sensitive data (email account information), and limited
+ management number of email accounts i.e. swapping different email services like Gmail and Outlook
+### Adding a Product
+
+#### Implementation
+
+The **Adding a Product** mechanism is facilitated by `MyCRM`. This product created is stored internally using
+`UniqueProductList` inside the `MyCRM` object.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `addProduct` command behave when executed by user:
+
+[![](images/product/AddProductActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/product/AddProductActivityDiagram.png)
+
+Given below is an example usage scenario and how the mechanism behaves at each step.
+
+[![](images/product/AddProductSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/product/AddProductSequenceDiagram.png)
+
+Note: Please refer to [StateManager](#statemanager) for more details on why the command result of the AddProductCommand is intercepted.
+
+**Parse user input**
+
+Within `AddProductCommandParser#parse`, the **factory methods** of product components (`getProductName` and
+`getEmptyProductName` for product name, `getType` and `getEmptyType` for type, ...) will be invoked to create product
+component objects: name, type, manufacturer, description.
+
+**Note**: Name is *compulsory* for creating a product, whereas type, manufacturer and description are *optional* fields.
+
+[![](images/product/AddProductSequenceDiagram_Parse.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/product/AddProductSequenceDiagram_Parse.png)
+
+### Editing a Product
+
+#### Implementation
+
+The **Editing a Product** mechanism is facilitated by `MyCRM`. This mechanism first reads the target product object from
+`UniqueProductList`, then creates a new product object with user input fields and unchanged fields of target
+product. Lastly, it replaces the target product with the new one, updates its references in jobs, and updates UI.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `editProduct` command behave when executed by user:
+
+[![](images/product/EditProductActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/product/EditProductActivityDiagram.png)
+
+Given below is an example usage scenario and how the mechanism behaves at each step.
+
+[![](images/product/EditProductSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/product/EditProductSequenceDiagram.png)
+
+**Parse user input**
+
+Within `EditProductCommandParser#parse`,
+* EditProductCommandParser will only get the values of fields(`name`, `manufacturer`, `type`, `description`) if their
+ respective prefixes are present.
+
+`EditTemplateCommandParser#parse` will call `ArgumentMultimap#getPreamble` to get the specified product index and
+`ArgumentMultimap#getValue` to extract product name “Asus” and description “DisplayPort, HDMI” from user input
+respectively.
+
+[![](images/product/EditProductSequenceDiagram_Parse.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/product/EditProductSequenceDiagram_Parse.png)
+
+**Updates product references in jobs**
+
+After target product is replaced with new product, `EditProductCommand#execute()` will traverse job list and replace
+references to target product with references to new product.
+
+To get the full job list, `EditProductCommand#execute()` will store the *lastest predicate* of job list and set the
+predicate to "show all jobs". After traversing the job list and updating the references, the *latest predicate* is
+restored.
+
+Design Consideration: An alternative way to get full job list is to retrieve the underlying `UniqueJobList` through
+`Model`. `EditProductCommand` is implemented in the other way as directly accessing and modifying `UniqueJobList` leads to an
+association between `EditProductCommand` and `UniqueJobList`, which increases coupling.
+
+[![](images/product/EditProductSequenceDiagram_Sync.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/product/EditProductSequenceDiagram_Sync.png)
+
+### StateManager
+
+#### Motivation
+
+As seen from the description of the [Model Component](#model-component) the job entity holds a reference to exactly one contact and product entity.
+As such, one key consideration behind job related commands was providing an easy and flexible way for users to choose to assign either
+existing or new contact and/or products to jobs. As an example, when a user is adding a new job, the product or contact information needed might already exist in MyCRM,
+if there have been prior repairs on the same product or if it is a repeat client. In such cases the user will want to assign an existing contact and/or product to the new job.
+In other cases a new job will require creation of new contact and product entities not already present in MyCRM and subsequent assignment of these new contact and/or product entities
+to the job. The goal of `StateManager` is to handle these various scenarios amd make the process of assigning contacts and products to jobs easy for the user.
+
+#### Implementation
+
+The description of the [Logic Component](#logic-component) describes at a high level how `StateManager` controls which commands can be executed by `LogicManager`
+and how `StateManager` intercepts results of a `Command` to perform additional operations (if needed) by communicating with the `Model`.
+
+Here is a more detailed look at how `StateManager` works:
+1. Internally it has a list of possible states of the application. Based on the commands executed it remembers the current state of the application.
+ e.g. If a `addJob` command is issued without a contact index, that job will not be added to `MyCRM` immediately.
+ Instead, StateManager` will remember that a job is being added, and needs a contact assigned to it.
+2. Each state has a corresponding list of allowed commands that are permitted in that state. It prevents `LogicManager` from executing commands not permitted for the
+ current state. e.g When a contact is being assigned to a job, some commands that are allowed (non-exhaustive) are `addContact`, `listContact`, `findContact` etc...
+3. When a command is executed, the state manager might intercept its `CommandResult`, to perform additional operations and update its internal state.
+ e.g After an `addContact` command, `StateManager` will check the current state to see if a job is currently being added. If so state manager will link it to that job.
+ If the job is complete (does not require assignment of a product), it will be added to `MyCRM` and the job creation will be complete. Otherwise, `StateManager` will update
+ its internal state to remember that a job is being added, and needs a product assigned to it.
+4. The `CommandResult` is modified suitably based on additional operations performed before being returned to `LogicManager`. This is so that the user is able to see
+ appropriate feedback on what operations were completed.
+
+While the above examples were in the context of `addJob` commands, `StateManager` plays a identical role in helping with the reassignment of contact and/or product for existing jobs, via the
+`editJob` command.
+
+The two activity diagrams below illustrates the events that might occur when MyCRM prompts the user to assign a contact and/or product to a job. This can happen after the user
+has issued an `addJob` or `editJob` command without a contact or product index.
+
+[![Activity diagram of assigning contact to job](images/job/AssigningContactActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/job/AssigningContactActivityDiagram.png)
+
+[![Activity diagram of assigning product to job](images/job/AssigningProductActivtyDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/job/AssigningProductActivtyDiagram.png)
+
+Notice that the user can still choose to select an existing contact and/or product even if their index wasnt provided in the `addJob` or `editJob` command itself.
+This is made possible using a `select` command.
+
+Also notice that the user can call an `abort` command to exit from the current operation.
+`StateManager` will clear its state, and the adding or editing of the job will not go through.
+
+### Adding a Job
+
+#### Implementation
+
+The **Adding a Job** mechanism is facilitated by `MyCRM`. Similar to other entities like products or contacts, jobs created
+are stored internally using`UniqueJobList` inside the `MyCrm` object.
+
+#### Usage
+
+The activity diagram below illustrates the possible workflows when the `addJob` command is being executed by the user.
+
+[![Activity diagram of add job](images/job/AddJobActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/job/AddJobActivityDiagram.png)
+
+Notice that in the case the contact and/or product index are not provided `StateManager` will prompt for the two entities sequentially.
+First it checks if the contact has been assigned, and then it checks if the product is assigned.
+If only one of contact or product index is missing, `StateManager` will only ask for assignment of the missing entity.
+
+In the [Logic Component](#logic-component) a high level sequence diagram is shown for when both the contact or product index are provided, and the job is added to MyCRM.
+
+The below sequence diagram shows an example usage scenario where the product index is missing.
+Notice how the job is passed to the `StateManager` but not added to the model, because the job does not have a product assigned.
+
+[![Sequence diagram of add job](images/job/AddJobSequenceDiagramShort.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/job/AddJobSequenceDiagramShort.png)
+
+**Parsing of user input**
+
+As seen from the description of the [Model Component](#model-component) the job entity has many subcomponents such as `JobDescription`, `JobFee`,
+`JobDate`, etc... Within `AddJobCommandParser#parse`, methods from `ParserUtil` are used to create these job subcomponents, before creating the job to be added to `MyCRM`.
+
+[![Sequence diagram of parsing user input](images/job/AddJobParseSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/job/AddJobParseSequenceDiagram.png)
+
+### Editing a job
+
+#### Implementation
+The **Editing a Job** mechanism is facilitated by `MyCRM`. Similar to the other entities, this mechanism first reads the target job object from
+`UniqueJobList`, and then creates a new job object with the user input fields in the `editJob` command and unchanged fields of target
+job. Lastly, it replaces the target job with the new one and is reflected in the updated UI.
+
+#### Usage
+
+The activity diagram below illustrates the possible workflows when the `editJob` command is being executed by the user.
+
+Notice one difference between the workflow between `addJob` and `editJob`. Even though the user doesn't have to provide the contact or product index in the command itself,
+the user still has to indicate an intention to reassign the contact and/or product of the existing job. This is done by providing the contact or product prefixes in the command
+without their index, e.g `editJob INDEX c/ p/`
+
+[![Activity diagram of edit job](images/job/EditJobActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/job/EditJobActivityDiagram.png)
+
+The below sequence diagram shows an example usage scenario where both the contact and product indexes are present.
+
+[![Sequence diagram of edit job](images/job/EditJobSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/job/EditJobSequenceDiagram.png)
+
+The parsing for `editJob` is similar to `addJob` where methods from `ParserUtil` are used to create the sub-components,
+before creating the edited version of the job.
+
+### Printing a monthly job report
+
+#### Implementation
+
+The **Printing a monthly job report** mechanism is facilitated by `MyCRM`. This job report is generated based of the information from
+jobs completed in this month and received in this month from `UniqueJobList` inside `MyCRM` object. A report window
+will be created with details of completed jobs, in-progress jobs, top three popular products, and a bar graph showing monthly revenue.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `printReport` command behave when executed by user:
+
+[![Activity diagram of print report](images/report/PrintReportActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/report/PrintReportActivityDiagram.png)
+
+Given below is an example usage scenario and how the mechanism behaves at each step.
+
+[![Sequence diagram of print report parser](images/report/PrintReportParserSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/report/PrintReportParserSequenceDiagram.png)
+
+Within `PrintReportCommandParser#parse`,
+- `CommandFlag` is optional but if provided, it can only be either **"-i"** or **"-p"**.
+- If **"-i"** is presented, report window will show all in-progress jobs received in this month.
+- If **"-i"** is presented, report window will show the top three popular products in this month.
+- if no flag is presented, report windwo will shou all jobs completed in this month by default.
+
+`PrintReportCommandParser#parse` will call `String#trim` to get specific command flag.
+
+[![Sequence diagram of print report](images/report/PrintReportSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/report/PrintReportSequenceDiagram.png)
+
+### Exporting a monthly job report
+
+#### Implementation
+
+The **Exporting a monthly job report** mechanism is facilitated by `MyCRM`. This job report is generated based of the information from
+jobs completed in this month and received in this month from `UniqueJobList` inside `MyCRM` object. Then `MyCRM` will export
+the job report by printer or as PDF format.
+
+#### Usage
+
+The activity diagram below illustrates how the events of `printReport` command behave when executed by user:
+
+[![Activity diagram of export report](images/report/ExportReportActivityDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/report/ExportReportActivityDiagram.png)
+
+Given below is an example usage scenario and how the mechanism behaves at each step.
+
+[![Sequence diagram of export report parser](images/report/ExportReportParserSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/report/ExportReportParserSequenceDiagram.png)
+
+[![Sequence diagram of export report](images/report/ExportReportSequenceDiagram.png)](https://ay2122s1-cs2103-t14-3.github.io/tp/images/report/ExportReportSequenceDiagram.png)
+
+The implementation detail of `fillInnerParts` method call is similar to `printReport`.
--------------------------------------------------------------------------------------------------------------------
@@ -257,121 +832,1632 @@ _{Explain here how the data archiving feature will be implemented}_
**Target user profile**:
-* has a need to manage a significant number of contacts
+* is a tech-savvy computer repair shop technician.
+* owns a business repairing computers and laptops, actively servicing multiple clients and answering their queries.
+* has a need to manage a wide range of models and deals with both hardware and software issues.
+* has multiple repair-phases which have to be updated to clients.
* prefer desktop apps over other types
* can type fast
* prefers typing to mouse interactions
* is reasonably comfortable using CLI apps
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**:
+* manages clients and jobs faster than a typical mouse/GUI driven app
+* centralizes jobs and client information
+* integrates both status tracking and client notification/mailing
+* automates monthly reports and statistics generation
### 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… |
+| -------- | ---------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |
+| `*` | potential user | see the app populated with sample data | easily see how the app will look like when it is in use |
+| `*` | user ready to start | purge all current data | Get rid of sample data and begin tinkering / exploring the app |
+| `* *` | new user | view a guide | familiarize with the text-input commands |
+| `* * *` | user | send out emails | easily notify clients that their repair job has been completed |
+| `* * *` | user | create new jobs | begin tracking a new repair job |
+| `* * *` | user | edit existing jobs | amend details pertaining to a job |
+| `* * *` | user | create new contacts | notify a client of his/her job status (like completion, etc.) |
+| `* * *` | user | edit existing contacts | change information about a client |
+| `* * *` | user | link an existing contact to a job | reuse the contact information of a returning client for a new job |
+| `* * *` | user | create new products | refer to the relevant details of how to repair a specific device |
+| `* * *` | user | edit existing products | change repair details or method about a product |
+| `* * *` | user | link an existing product to a job | reuse the product information of a previous for a new job |
+| `* * *` | user | search for jobs using job status, service tag, client name or product name | find the jobs that match the specification |
+| `* *` | regular user | print out monthly job records and statistics | learn about the month’s performance and analyze how to improve |
+| `* *` | regular user | print out next month’s scheduled / on-going job | plan ahead resources for the coming month |
+| `*` | regular user | hide unused job fields | not be distracted by empty / irrelevant fields. |
+| `*` | regular user | hide unused contacts | not be distracted by irrelevant clients. |
+| `*` | regular user | customize the app’s user interface (like font and colour) | make the interface look more stylish and pleasant for the eyes |
+| `* *` | regular user | export my monthly records and statistics | store my record externally for future reference |
### 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 `MyCRM` and the **Actor** is the `user`, unless specified otherwise)
-**Use case: Delete a person**
+**Use case: UC01 - Adding a repair job**
**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 provides details to add a repair job.
+2. MyCRM creates a new repair job with the provided job description and associates a repair job with
+ a product and contact.
- Use case ends.
+ Use case ends.
**Extensions**
-* 2a. The list is empty.
+* 1a. User does not provide all the necessary details needed for the creation of a job.
+
+ * 1a1. MyCRM shows an error message and requests for the missing details.
+ * 1a2. User enters the missing details.
+
+ Steps 1a1-1a2 are repeated until the user enters the details. Use case resumes at step 2.
+
+**Use case: UC02 - Editing a repair job**
+
+**MSS**
+
+1. User requests to edit a repair job.
+2. MyCRM shows the list of repair jobs.
+3. User selects a repair job from the list.
+4. User provides details about fields they want to update.
+5. MyCRM updates the fields of the repair job with the information provided.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list of repair jobs is empty.
Use case ends.
-* 3a. The given index is invalid.
+* 3a. User selects invalid repair job not in the list.
- * 3a1. AddressBook shows an error message.
+ * 3a1. MyCRM shows an error message and asks user to re-select a repair job.
+ * 3a2. User re-selects a repair job.
- Use case resumes at step 2.
+ Steps 3a1-3a2 are repeated until the user selects a valid repair job. Use case resumes at step 4.
-*{More to be added}*
+* 4a. The given fields provided by the User are invalid.
-### Non-Functional Requirements
+ * 4a1. MyCRM shows an error message and asks user for the details they want to update again.
+ * 4a2. User provides the details they want to update.
-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.
+ Steps 4a1-4a2 are repeated until the user provided valid details. Use case resumes at step 5.
-*{More to be added}*
+**Use case: UC03 - Deleting a repair job**
-### Glossary
+**MSS**
-* **Mainstream OS**: Windows, Linux, Unix, OS-X
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+1. User requests to delete a repair job.
+2. MyCRM shows a list of repair jobs.
+3. User selects a repair job from the list which they want to delete.
+4. MyCRM deletes the repair job.
---------------------------------------------------------------------------------------------------------------------
+ Use case ends.
-## **Appendix: Instructions for manual testing**
+**Extensions**
-Given below are instructions to test the app manually.
+* 2a. The list is empty.
-:information_source: **Note:** These instructions only provide a starting point for testers to work on;
-testers are expected to do more *exploratory* testing.
+ Use case ends.
-
+* 3a. User selects invalid repair job not in the list.
-### Launch and shutdown
+ * 3a1. MyCRM shows an error message and asks user to re-select a repair job.
+ * 3a2. User re-selects a repair job they want to delete.
-1. Initial launch
+ Steps 3a1-3a2 are repeated until the user selects a valid repair job. Use case resumes at step 4.
- 1. Download the jar file and copy into an empty folder
+**Use case: UC04 - Mark a repair job as completed**
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+**MSS**
-1. Saving window preferences
+1. User requests to mark a repair job as completed.
+2. MyCRM shows a list of repair jobs.
+3. User selects a repair job from the list which they want to mark as completed.
+4. MyCRM marks the repair job as complete, and hides the repair job from the job list.
- 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
+ Use case ends.
- 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained.
+**Extensions**
-1. _{ more test cases … }_
+* 2a. The list is empty.
-### Deleting a person
+ Use case ends.
-1. Deleting a person while all persons are being shown
+* 3a. User selects invalid repair job not in the list.
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+ * 3a1. MyCRM shows an error message and asks user to re-select a repair job.
+ * 3a2. User re-selects a repair job they want to hide.
- 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.
+ Steps 3a1-3a2 are repeated until the user selects a valid repair job. Use case resumes at step 4.
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+* 3b. User selects a job that is already marked as completed.
+ * 3b1. MyCRM shows an error message telling the user the job has
+ already been marked as completed.
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous.
+ Use case ends.
-1. _{ more test cases … }_
+**Use case: UC05 - List repair jobs**
-### Saving data
+**MSS**
+
+1. User requests to list all repair jobs.
+2. MyCRM shows a list of repair jobs.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
-1. Dealing with missing/corrupted data files
+ Use case ends.
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+**Use case: UC06 - Find a repair job**
+
+**MSS**
+
+1. User wants to find a repair job and provides keywords they want to search by.
+2. MyCRM shows a list of filtered repair jobs for which the keywords
+ appear in the job's description, contact or product.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. No repair job matches any of the keywords provided by the user.
+
+ Use case ends.
+
+**Use case: UC07 - Adding a client contact**
+
+**MSS**
+
+1. User requests to add a contact with specific info of name, contact number, address, and email.
+2. MyCRM stores the new contact in the contact list.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given contact name is empty.
+
+ * 1a1. MyCRM shows an error message.
+
+ Use case resumes at step 1.
+
+* 1b. Either the given contact number, address or email is empty.
+
+ * 1b1. MyCRM shows an error message.
+
+ Use case resumes at step 1.
+
+* 1c. The given contact name already exists.
+
+ * 1c1. MyCRM shows an error message.
+
+ Use case resumes at step 1.
+
+**Use case: UC08 - Editing a client contact**
+
+**MSS**
+
+1. User requests to edit a contact.
+2. MyCRM shows a list of contacts.
+3. User requests to edit a specific contact's info with specific index and type of the field in contact.
+4. MyCRM updates this specific contact's info.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+* 3b. The given edit field type is invalid.
+
+ * 3b1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC09 - Deleting a client contact**
+
+**MSS**
+
+1. User requests to delete a contact.
+2. MyCRM shows a list of contacts.
+3. User requests to delete a specific contact
+4. MyCRM deletes the contact.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+
+**Use case: UC10 - Hiding a client contact**
+
+**MSS**
+
+1. User requests to hide a contact.
+2. MyCRM shows a list of contacts.
+3. User requests to hide a specific contact in the list.
+4. MyCRM tags the contact as hidden.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC11 - Undo hiding a client contact**
+
+**MSS**
+
+1. User requests to undo hiding a hidden contact.
+2. MyCRM shows a list of contacts.
+3. User requests to undo hiding the specific contact in the list.
+4. MyCRM undo the hidden tag for this contact.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC12 - Sending an email**
+
+**Precondition:** Operating system has a default email application
+
+**MSS**
+
+1. User requests to send an email.
+2. MyCRM shows a list of jobs.
+3. User requests to email a specific job in the list.
+4. MyCRM shows a list of email templates.
+5. User requests to copy a specific email template.
+6. MyCRM generates mailto link of the job and template
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list of jobs is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+* 3b. The job at given index does not have an email.
+
+ * 3a1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+* 4a. The list of templates is empty.
+
+ Use case ends.
+
+* 5a. The given index is invalid.
+
+ * 3a1. MyCRM shows an error message.
+
+ Use case resumes at step 4.
+
+**Use case: UC13 - Adding an email template**
+
+**MSS**
+
+1. User requests to add an email template with specific subject and body.
+2. MyCRM creates email template.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given subject is empty.
+
+ * 1a1. MyCRM shows an error message.
+
+ Use case resumes at step 1.
+
+* 1b. The given subject is invalid format.
+
+ * 1b1. MyCRM shows an error message.
+
+ Use case resumes at step 1.
+
+* 1c. The given body is empty.
+
+ * 1c1. MyCRM shows an error message.
+
+ Use case resumes at step 1.
+
+**Use case: UC14 - Listing all email template**
+
+**MSS**
+
+1. User requests to view all email template.
+2. MyCRM shows a list of email template.
+
+ Use case ends.
+
+**Use case: UC15 - Listing all email template**
+
+**MSS**
+
+1. User request to find a template of specified subject keyword(s).
+2. MyCRM shows a list of filtered template for which the keywords appear in the template's subject.
+ product.
+
+ Use case ends.
+
+
+**Use case: UC16 - Editing an email template**
+
+**MSS**
+
+1. User request to edit an email template.
+2. MyCRM shows a list of email template.
+3. User requests to edit a specific template's subject or body (or both) in the list.
+4. MyCRM modifies the template with new subject or body (or both).
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+* 3b. The given subject is empty
+
+ * 3b1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+* 3c. The given subject is invalid format.
+
+ * 3c1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+* 3d. The given body is empty.
+
+ * 3d1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC17 - Deleting an email template**
+
+**MSS**
+
+1. User requests to delete an email template.
+2. MyCRM shows a list of email template.
+3. User requests to delete a specific template in the list.
+4. MyCRM shows deletes the template.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. MyCRM shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC18 - Finding an email template**
+
+**MSS**
+
+1. User wants to find an email template job and provides keywords they want to search by.
+2. MyCRM shows a list of filtered templates for which the keywords appear in the template's subject.
+
+ Use case ends.
+
+**Use case: UC19 - Viewing user guide**
+
+**MSS**
+
+1. User requests to view the user guide.
+2. MyCRM shows a popup with the GitHub user guide URL.
+
+ Use case ends.
+
+**Use case: UC20 - Exiting the program**
+
+**Postcondition:** MyCRM application closes.
+
+**MSS**
+
+1. User requests to exit MyCRM.
+2. MyCRM shows a goodbye message.
+
+ Use case ends.
+
+**Use case: UC21 - Clearing MyCRM data**
+
+**Postcondition:** MyCRM data of contacts, products, and templates are empty.
+
+**MSS**
+
+1. User request to clear data.
+2. MyCRM removes data from job, contact, product list.
+
+ Use case ends.
+
+**Use case: UC22 - Add Product**
+
+**MSS**
+
+1. User requests to add a new product.
+2. MyCRM creates a new product and shows a message with info of the product.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The product name already exists.
+ * MyCRM shows an error message.
+
+ Use case ends.
+
+
+* 2b. The product name is empty.
+ * MyCRM shows an error message.
+
+ Use case ends.
+
+**Use case: UC23 - List Products**
+
+**MSS**
+
+1. User requests to view the list of products.
+2. MyCRM shows the list of products.
+
+ Use case ends.
+
+**Extensions**
+* 2a. The list of products is empty.
+
+ Use case ends.
+
+**Use case: UC24: Delete a product**
+
+**MSS**
+
+1. User requests to delete a specific product in the list.
+2. MyCRM deletes the product.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given index is invalid.
+ * 1a1. MyCRM shows an error message.
+
+ Use case ends.
+
+* 1b. The specified product is linked to one or more jobs.
+ * 1b1. MyCRM shows an error message.
+
+ Use case ends.
+
+**Use case: UC25: Edit a product.**
+
+**MSS**
+
+1. User requests to edit a specific product in the list.
+2. MyCRM edits the product and shows a success message with info of edited product.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given index is invalid.
+ * 1a1. MyCRM shows an error message.
+
+ Use case ends.
+
+* 1b. User requests to edit the name of the product.
+ * 1b1. The product name already exists
+ * 1b2. MyCRM shows an error message.
+
+ Use case ends.
+
+* 1c. All fields that user provides are empty.
+ * 1c1. MyCRM shows an error message.
+
+ Use case ends.
+
+**Use case: UC26 - Retrieve Previous Command**
+
+**MSS**
+
+1. User requests to retrieve previously entered command.
+2. MyCRM shows the previous command.
+
+ Use case ends.
+
+**Extensions**
+* 2a. MyCRM show the most recent command.
+
+ Use case ends.
+
+* 2b. MyCRM list all history commands.
+
+ Use case ends.
+
+**Use case: UC27 - Change the theme of user interface(UI)**
+
+**MSS**
+
+1. User requests to change the theme of UI.
+2. MyCRM changes the theme of Ui.
+
+ Use case ends.
+
+**Extensions**
+* 1a. User enters a theme name that does not exist in MyCRM.
+ * 1a1. MyCRM shows an error message.
+
+ Use case ends.
+
+* 1b. User enters the name of current theme.
+ * 1a1. MyCRM keeps the current theme.
+
+ Use case ends.
+
+**Use case: UC28 - Print out monthly job records and statistics**
+
+**MSS**
+
+1. User requests to print out monthly job report.
+2. MyCRM shows the monthly job report in a new window.
+
+ Use case ends.
+
+**Extensions**
+* 1a. User requests to print out monthly job report when report window is not shown.
+ * 1a1. MyCRM shows the report window.
+
+ Use case ends.
+
+* 1b. User requests to print out monthly job report when report window is shown.
+ * 1b1. MyCRM focuses on the report window.
+
+ Use case ends.
+
+**Use case: UC29 - Export monthly job records and statistics**
+
+**MSS**
+
+1. User requests to export monthly job report.
+2. MyCRM show the page setup dialog and print dialog.
+
+
+### 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 100 _entries_ without a noticeable sluggishness in performance for typical usage.
+3. Should be designed for a single-user.
+4. Should work without an internet connection.
+5. Should be accessible via the downloaded JAR file without any other installations needed.
+6. Should take up less than 50 MB computer storage.
+7. Should work on both 32-bit and 64-bit environments.
+8. 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.
+
+### Glossary
+
+* **Mainstream OS**: Windows, Linux, Unix, OS-X.
+* **Private contact detail**: A contact detail that is not meant to be shared with others.
+* **Repair Job**: A customer request to restore machinery, equipment, or other products to working order.
+* **Job Status**: A progress bar showing the customer's job's degree of completion.
+* **JSON**: Javascript Standard Object Notation, which is a form of syntax used for storing data.
+* **mailto**: A Uniform Resource Identifier scheme for email addresses, produces hyperlinks on websites that allow
+ users to send an email.
+* **URL**: A Uniform Resource Locators is a unique identifier commonly used to access a resource on the Internet.
+* **Entry**: Contact/job/product.
+
+--------------------------------------------------------------------------------------------------------------------
+
+## **Appendix: Instructions for manual testing**
+
+Given below are instructions to test the app manually.
+
+:information_source: **Note:** These instructions only provide a starting point for testers to work on;
+testers are expected to do more *exploratory* testing.
+
+
+
+### Launch
+
+1. Initial launch
+
+ 1. Download the jar file and copy into an empty folder
+
+ 2. Double-click the jar file.
+
Expected: Shows the GUI with a set of sample contacts, products, templates and job. The window size may
+ not be optimum.
+
+2. Saving window preferences
+
+ 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
+
+ 2. Re-launch the app by double-clicking the jar file.
+
Expected: The most recent window size and location is retained.
+
+1. Saving theme preferences
+
+ 1. [Choose a theme](UserGuide.md#changing-the-theme-of-user-interface-theme) of GUI. Close the window.
+
+ 1. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent theme is retained.
+
+### Shutdown
+
+1. Exiting application
+
+ 1. Test case: `exit`
+
Expected: GUI closes and application is shutdown.
+
+ 2. Test case: Click the exit 'x' (cross)
+
Expected: Similar to previous
+
+### Requesting help
+
+1. Opening help page
+
+ 1. Test case: `help`
+
Expected: Shows the help GUI with user guide details.
+
+ 2. Test case: Press F1
+
Expected: Similar to previous
+
+ 3. Test case: Click "Help" > "Help F1"
+
Expected: Similar to previous
+
+### Adding contact
+
+1. Inserting a contact while all contacts are being shown.
+
+ 1. Prerequisites: List contacts using the `listContact` command. Shows list of contacts in side panel.
+
+ 2. Test case: `addContact n/Jack Ryan c/94678954 a/Blk 65 Tampines Ave 1 e/jryan@gmail.com`
+
Expected:
+ - New contact is added to the list.
+ - Details of contact shown in status message.
+
+ 3. Test case: `addContact n/Sans c/85230240 a/Clementi Ave 1 e/Sans@gmail.com`
+
Expected:
+ - New contact is added to the list.
+ - Details of contact shown in status message.
+
+ 4. Test case: `addContact n/Jack Ryan c/94678954 a/Blk 65 Tampines Ave 1 e/jryan@gmail.com`
+
Expected:
+ - No new contact is added
+ - Command error for duplicate contact is shown in status message.
+
+ 5. Test case: `addContact n/Jay`
+
Expected:
+ - Similar to previous.
+ - Command error for at least one field of phone, email and address required is shown in status message.
+
+ 6. Test case: `addContact n/Jay&sda c/83336233`
+
Expected:
+ - Similar to previous.
+ - Command error for name format details is shown in status message.
+
+ 7. Test case: `addContact n/Jay c/abcd`
+
Expected:
+ - Similar to previous.
+ - Command error for phone format details is shown in status message.
+
+ 8. Test case: `addContact n/Jay e/abcd`
+
Expected:
+ - Similar to previous.
+ - Command error for email format details is shown in status message.
+
+ 9. Test case: `addContact n/Jay a/`
+
Expected:
+ - Similar to previous.
+ - Command error for address format details is shown in status message.
+
+2. Inserting a contact while all contacts are NOT being shown.
+
+ 1. Prerequisites: List other data types i.e. `listTemplate` command. Shows list of other data in side panel.
+
+ 2. Correct test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list all contacts.
+
+ 3. Other incorrect test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel remains at current list. No new contacts are added.
+
+### Editing a contact
+
+1. Editing a contact while all contacts are being shown.
+
+ 1. Prerequisites: List contacts using the `listContact` command. Shows list of contacts in side panel.
+
+ 2. Test case: `editContact 1 n/Jacky e/Jacky@gmail.com`
+
Expected:
+ - Contact index 1 is updated in the list.
+ - Details of contact shown in status message.
+
+ 3. Test case: `editContact 2 n/Jacky`
+
Expected:
+ - No contact is edited.
+ - Command error for duplicate contact is shown in status message.
+
+ 4. Test case: `editContact 1`
+
Expected:
+ - Similar to previous.
+ - Command error for at least one field of name, phone, email, address or tag required is shown in status message.
+
+ 5. Test case: `editContact 1 n/Jay&sda`
+
Expected:
+ - Similar to previous.
+ - Command error for name format details is shown in status message.
+
+ 6. Test case: `editContact 1 c/abcd`
+
Expected:
+ - Similar to previous.
+ - Command error for phone format details is shown in status message.
+
+ 7. Test case: `editContact 1 e/abcd`
+
Expected:
+ - Similar to previous.
+ - Command error for email format details is shown in status message.
+
+ 8. Test case: `editContact 1 a/`
+
Expected:
+ - Similar to previous.
+ - Command error for address format details is shown in status message.
+
+2. Editing a contact while all contacts are NOT being shown.
+
+ 1. Prerequisites: List other data types i.e. `listTemplate` command. Shows list of other data in side panel.
+
+ 2. Correct test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list all contacts.
+
+ 3. Other incorrect test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel remains at current list. No new contacts are edited.
+
+
+### Deleting a contact
+
+1. Deleting a contact while all contacts are being shown.
+
+ 1. Prerequisites: List contacts using the `listContact` command. Shows list of contacts in side panel.
+
+ 2. Prerequisites: No contacts are linked to a job.
+
+ 3. Test case: `deleteContact 1`
+
Expected:
+ - Contact index 1 is deleted in the list.
+ - Details of contact shown in status message.
+
+ 4. Test case: `deleteContact -1`
+
Expected:
+ - Similar to previous.
+ - Command error for invalid index is shown in status message.
+
+2. Deleting a contact while all contacts are being shown.
+
+ 1. Prerequisites: List contacts using the `listContact` command. Shows list of contacts in side panel.
+
+ 2. Prerequisites: All contacts are linked to a job.
+
+ 3. Test case: `deleteContact 1`
+
Expected:
+ - No contacts are deleted.
+ - Command error for invalid contact deletion requirement is shown in status message.
+
+ 4. Test case: `deleteContact -1`
+
Expected:
+ - Similar to previous.
+ - Command error for invalid index is shown in status message.
+
+3. Deleting a contact while all contacts are NOT being shown.
+
+ 1. Prerequisites: List other data types i.e. `listTemplate` command. Shows list of other data in side panel.
+
+ 2. Prerequisites: No contacts are linked to a job.
+
+ 3. Correct test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list updated contact list.
+
+ 4. Other incorrect test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel remains at current list. No contacts are deleted.
+
+
+### Finding contacts
+
+1. Finding contacts while all contacts are being shown.
+
+ 1. Prerequisites: List contacts using the `listContact` command. Shows list of contacts in side panel.
+
+ 2. Test case: `findContact Jacky`
+
Expected:
+ - Matching contacts are shown on list.
+ - Number of contacts found shown in status message.
+
+ 3. Test case: `findContact abcd`
+
Expected:
+ - No matching contacts are shown on list.
+ - Zero contacts found shown in status message.
+
+2. Finding contacts while all contacts are NOT being shown.
+
+ 1. Prerequisites: List other data types i.e. `listTemplate` command. Shows list of other data in side panel.
+
+ 2. Test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list matching all contacts.
+
+### Hiding contacts
+
+1. Hiding a contact while all contacts are being shown.
+
+ 1. Prerequisites: List contacts using the `listContact` command. Shows unhidden list of contacts in side panel.
+
+ 2. Test case: `hideContact 1`
+
Expected:
+ - Contact 1 is hidden on list.
+ - Side panel will update list of all not hidden contacts.
+
+ 3. Test case: `hideContact -1`
+
Expected:
+ - No contact is hidden on list.
+ - Side panel will remain the current contact list.
+
+2. Hiding a contact while all contacts are NOT being shown.
+
+ 1. Prerequisites: List other data types i.e. `listTemplate` command. Shows list of other data in side panel.
+
+ 2. Correct test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will update contact list.
+
+ 3. Incorrect test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will remain current contact list.
+
+### Undoing hiding contacts
+
+1. Undo hiding a contact while all contacts are being shown.
+
+ 1. Prerequisites: List contacts using the `listContact -a` command. Shows unhidden list of contacts in side panel.
+
+ 2. Test case: `undoHideContact 1`
+
Expected:
+ - Contact 1 is hidden on list.
+ - Side panel will update list of all not hidden contacts.
+
+ 3. Test case: `undoHideContact -1`
+
Expected:
+ - No contact is hidden on list.
+ - Side panel will remain the current contact list.
+
+### Listing contacts
+
+1. Test case: `listContact`
+
Expected:
+ - Side panel will list all not hidden contacts.
+
+2. Test case: `listContact -a`
+
Expected:
+ - Side panel will list all contacts.
+
+3. Test case: `listContact abcd`
+
Expected:
+ - Side panel will remain current list.
+ - Command error for invalid listContact format is shown in status message.
+
+
+### Constructing mail
+
+1. Constructing a mail when there is at least one job and mail.
+
+ 1. Prerequisites: There must be at least one job and mail in MyCRM. `addJob`, `addTemplate`
+
+ 2. Test case: `mail j/1 t/1`
+
Expected:
+ - New mail is added to MyCRM with first job and template.
+ - Details of mail shown in status message.
+ - Details of mail shown in main panel with mailtoURL.
+
+ 3. Test case: `mail j/1`
+
Expected:
+ - No mail is constructed.
+ - Format error details is shown in status message.
+
+ 4. Test case: `mail t/1`
+
Expected:
+ - Similar to previous
+
+ 5. Test case: `mail j/0 t/0`
+
Expected:
+ - Similar to previous
+ - Index error details is shown in status message.
+
+ 6. Test case: `mail j/1 t/-1`
+
Expected:
+ - Similar to previous
+
+### Adding template
+
+1. Inserting a template while all templates are being shown.
+
+ 1. Prerequisites: List all templates using the `listTemplate` command. Shows list of templates in side panel.
+
+ 2. Test case: `addTemplate s/ANewTemplate b/This is a new template!`
+
Expected:
+ - New template is added to the list.
+ - Details of template shown in status message.
+
+ 3. Test case: `addTemplate s/A New Template b/This is a new template!\nRegards`
+
Expected:
+ - Similar to previous
+ - New template body will represent '\n' as a newline.
+
+ 4. Test case: `addTemplate s/Completed b/Your order has been completed`
+
Expected:
+ - No template is added.
+ - Duplicate error details is shown in status message.
+
+ 5. Test case: `addTemplate s/!Inv@lid b/Your order has been completed`
+
Expected:
+ - Similar to previous
+ - Subject error details is shown in status message.
+
+ 6. Test case: `addTemplate s/Valid subject b/`
+
Expected:
+ - Similar to previous
+ - Body error details is shown in status message.
+
+2. Inserting a template while all templates are NOT being shown.
+
+ 1. Prerequisites: List other data types i.e. `listProduct` command. Shows list of other data in side panel.
+
+ 2. Correct test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list all templates.
+
+ 3. Other incorrect test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will NOT list all templates. Remains at current list.
+
+### Editing template
+
+1. Editing a template while all templates are being shown.
+
+ 1. Prerequisites: List all templates using the `listTemplate` command. Shows list of templates in side panel.
+
+ 2. Test case: `editTemplate 1 s/New Completed`
+
Expected:
+ - First template is updated to the list.
+ - Details of template shown in status message.
+
+ 3. Test case: `editTemplate 1 b/New Body`
+
Expected:
+ - Similar to previous.
+
+ 4. Test case: `editTemplate 1 s/Brand New Completed b/Brand New Body`
+
Expected:
+ - Similar to previous.
+
+ 5. Test case: `editTemplate 1 s/!Inv@lid`
+
Expected:
+ - No template is edited.
+ - Subject error details is shown in status message.
+
+ 6. Test case: `editTemplate 1 b/ `
+
Expected:
+ - Similar to previous
+ - Body error details is shown in status message.
+
+ 7. Test case: `editTemplate 0 s/3rd New Completed b/3rd New Body`
+
Expected:
+ - Similar to previous
+ - Format error details is shown in status message.
+
+2. Editing a template while all templates are not shown.
+
+ 1. Prerequisites: List other data types i.e. `listProduct` command. Shows list of other data in side panel.
+
+ 2. Correct test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list all templates.
+
+ 3. Other incorrect test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will NOT list all templates. Remains at current list.
+
+### Deleting template
+
+1. Deleting a template while all templates are being shown.
+
+ 1. Prerequisites: List all templates using the `listTemplate` command. Shows list of templates in side panel.
+
+ 2. Test case: `deleteTemplate 1`
+
Expected:
+ - First template is deleted to the list.
+ - Details of template shown in status message.
+
+ 3. Test case: `deleteTemplate 0`
+
Expected:
+ - Similar to previous
+ - Format error details is shown in status message.
+
+2. Deleting a template while all templates are not shown.
+
+ 1. Prerequisites: List other data types i.e. `listProduct` command. Shows list of other data in side panel.
+
+ 2. Correct test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list all templates.
+
+ 3. Other incorrect test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will NOT list all templates. Remains at current list.
+
+### Finding template
+
+1. Finding a template while all templates are being shown.
+
+ 1. Prerequisites: List all templates using the `listTemplate` command. Shows list of templates in side panel.
+
+ 2. Test case: `findTemplate Done`
+
Expected:
+ - Matching templates are shown on list.
+ - Number of template found shown in status message.
+
+ 3. Test case: `findTemplate Done New Template`
+
Expected:
+ - Similar to previous
+
+ 3. Test case: `findTemplate $`
+
Expected:
+ - No matching templates are shown on list.
+ - Zero templates found shown in status message.
+
+2. Finding a template while all templates are not shown.
+
+ 1. Prerequisites: List other data types i.e. `listProduct` command. Shows list of other data in side panel.
+
+ 2. Test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list all templates.
+
+### Saving data
+
+1. Dealing with missing data files
+
+ 1. To simulate a missing file, delete the myCrm.json file from the data directory.
+ 2. Re-launch the app by double-clicking the jar file.
+
Expected: The application will generate a new myCrm.json with sample data.
+
+2. Dealing with corrupted data files
+
+ 1. To simulate a corrupted file, open the myCrm.json file from data directory.
+ 2. Delete line 2 from the file.
+ 3. Re-launch the app by double-clicking the jar file.
+
Expected: The application will not load any data, empty content.
+
+3. Customising with JSON data file
+
+ 1. Test case: Customising contact
+ 1. Open the myCrm.json file from data directory.
+ 2. Modify first contact name i.e.'Damith Rajapakse'
+ 3. Re-launch the app by double-clicking the jar file.
+
Expected: The application will load data, first contact name is modified.
+
+ 2. Test case: Customising template
+ 1. Similar to previous
+ 2. Modify first subject i.e.'Welcome newcomer'
+
Expected: The application will load data, first subject is modified.
+
+ 3. Test case: Customising product
+ 1. Similar to previous
+ 2. Modify first product type i.e.'Computer Unit'
+
Expected: The application will load data, first product type is modified.
+
+ 4. Test case: Customising job
+ 1. Similar to previous
+ 2. Modify first job fee i.e.'$100.00'
+
Expected: The application will load data, first job fee is modified.
+
+ 5. Other correct test cases: Customising contact, template, product, job
+ 1. Similar to adding correct contact, template, product, job test case
+
Expected: The application will load data, data is modified.
+
+ 6. Other incorrect test cases: Customising contact, template, product, job
+ 1. Similar to adding incorrect contact, template, product, job test case
+
Expected: The application will not load any data, empty content.
+
+### Adding product
+
+1. Inserting a product while all products are being shown
+
+ 1. Prerequisites: List all products using the `listProduct` command. Shows list of products in side panel.
+
+ 2. Test case: `addProduct n/new product one t/GPU m/Asus`
+
Expected:
+ - New product is added to the list.
+ - Details of product shown in status message.
+
+ 3. Test case: `addProduct n/new product two`
+
Expected:
+ - Similar to previous.
+
+ 4. Test case: `addProduct n/ t/CPU m/Intel`
+
Expected:
+ - No product is added.
+ - Format error details is show in status message.
+
+ 5. Test case: `addProduct n/duplicate_product_name t/Motherboard m/Asus`
+
Expected:
+ - No product is added.
+ - Duplicate error details is shown in status message.
+
+2. Inserting a product while all products are NOT being shown.
+
+ 1. Prerequisites: List other data types i.e. `listTemplate` command. Shows list of other data in side panel.
+
+ 2. Correct test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list all products.
+
+ 3. Other incorrect test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will NOT list all products. Remains at current list.
+
+### Editing product
+
+1. Editing a product while all products are being shown
+
+ 1. Prerequisites: List all products using the `listProduct` command. Shows list of products in side panel.
+
+ 2. Test case: `editProduct 2 d/Video output interface: DisplayPort, HDMI`
+
Expected:
+ - Second product is updated to the list.
+ - Details of product shown in status message.
+
+ 3. Test case: `editProduct 2 m/GIGABYTE`
+
Expected:
+ - Similar to previous.
+
+ 4. Test case: `editProduct 2 n/duplicate product name`
+
Expected:
+ - No product is edited.
+ - Duplicate error details is shown in status message.
+
+ 5. Test case: `editProduct 0 m/GIGABYTE`
+
Expected:
+ - No product is edited.
+ - Format error details is show in status message.
+
+2. Editing a product while all products are NOT being shown.
+
+ 1. Prerequisites: List other data types i.e. `listTemplate` command. Shows list of other data in side panel.
+
+ 2. Correct test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list all products.
+
+ 3. Other incorrect test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will NOT list all products. Remains at current list.
+
+### Deleting Product
+
+1. Deleting a product while all products are being shown.
+
+ 1. Prerequisites: List all products using the `listProduct` command. Shows list of products in side panel.
+
+ 2. Test case: `deleteProduct 1`
+
Expected:
+ - First product is deleted to the list.
+ - Details of product shown in status message.
+
+ 3. Test case: `deleteProduct 0`
+
Expected:
+ - No product is deleted.
+ - Format error details is shown in status message.
+
+2. Deleting a product while all products are not shown.
+
+ 1. Prerequisites: List other data types i.e. `listTemplate` command. Shows list of other data in side panel.
+
+ 2. Correct test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list all products.
+
+ 3. Other incorrect test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will NOT list all products. Remains at current list.
+
+### Finding product
+
+1. Finding a product while all products are being shown.
+
+ 1. Prerequisites: List all products using the `listProduct` command. Shows list of products in side panel.
+
+ 2. Test case: `findProduct Asus`
+
Expected:
+ - Matching products are shown on list.
+ - Number of product found shown in status message.
+
+ 3. Test case: `findProduct Asus gpu`
+
Expected:
+ - Similar to previous
+
+ 3. Test case: `findProduct @`
+
Expected:
+ - No matching products are shown on list.
+ - Zero products found shown in status message.
+
+2. Finding a product while all products are not shown.
+
+ 1. Prerequisites: List other data types i.e. `listTemplate` command. Shows list of other data in side panel.
+
+ 2. Test cases similar to (1)
+
Expected:
+ - Similar to (1)
+ - Side panel will list all products.
+
+### Adding job
+
+1. Inserting a new job while all in-progress jobs are being shown.
+
+ 1. Prerequisites: List all in-progress jobs using the `listJob` command. Shows list of in-progress jobs in the main panel.
+ At least 1 contact and product must be present for below test cases.
+
+ 3. Test case: `addJob d/CPU replacement needed fee/$30.00 recv/08/11/2021 by/03/01/2022 c/1 p/1`
+
Expected:
+ - New job is added to the job list. Job should be linked to first contact displayed currently in the
+ contact list and first product currently displayed in the product list.
+ - Details of job added shown in status message.
+
+ 4. Test case: `addJob d/CPU fix needed fee/$30.00 by/03/01/2022 c/1 p/1`
+
Expected:
+ - New job is added to the job list. Job should be linked to first contact currently displayed in the
+ contact list and first product currently displayed in the product list.
+ - Details of job added shown in status message.
+ - Since received date wasn't provided it should be automatically set to the current date.
+
+ 5. Test case: `addJob d/CPU fix needed fee/$30.00 by/03/01/2022 c/1 p/1`
+
Expected:
+ - No new job is added (provided the previous test case was run first)
+ - Error for duplicate job is shown in status message.
+
+ 6. Test case: `addJob d/CPU replacement fee/$30.00`
+
Expected:
+ - No new job is added
+ - Invalid command format error shown
+
+ 7. Test case: `addJob d/CPU replacement by/03/01/2021`
+
Expected:
+ - No new job is added
+ - Invalid command format error shown
+
+ 8. Test case: `addJob fee/$30.00 by/03/01/2022`
+
Expected:
+ - No new job is added
+ - Invalid command format error shown
+
+ 9. Test case:
+ Input these 3 commands one after another:
+ - `addJob d/Upgrade CPU fee/$100.00 by/05/01/2022`
+ - `select 1`
+ - `select 1`
+
Expected:
+ - New job is added to the job list. Job should be linked to first contact currently displayed in the
+ contact list and first product currently displayed in the product list.
+ - Details of job added shown in status message.
+
+2. Inserting a job while all completed jobs are being shown.
+
+ 1. Prerequisites: List all completed jobs using the `listJob -c` command. Shows list of completed jobs in the main panel.
+
+ 2. Test Case : `addJob d/CPU not working fee/$30.00 recv/08/11/2021 by/03/01/2022 c/1 p/1`
+
Expected:
+ - Main panel will switch to show all in-progress jobs.
+ - New in-progress job is added to the job list. Job should be linked to first contact currently displayed in the
+ contact list and first product currently displayed in the product list.
+ - Details of job added shown in status message
+
+### Editing a job
+
+1. Editing an in-progress job.
+
+ 1. Prerequisites: List all in-progress jobs using the `listJob` command. Shows list of in-progress jobs in the main panel.
+ Must have at least 1 in-progress job in the list for below test cases to work.
+
+ 2. Test case: `editJob 1 fee/$1000.00`
+
Expected:
+ - Job at index 1 in the currently displayed job list, should have its fee updated accordingly.
+ - Details of job edited shown in status message.
+
+ 3. Test case: `editJob 1 d/CPU Slow`
+
Expected:
+ - Job at index 1 in the currently displayed job list, should have its description updated accordingly.
+ - Details of job edited shown in status message.
+
+ 4. Test case: `editJob 1`
+
Expected:
+ - Job at index 1 in the currently displayed job list, should not be edited.
+ - Error shown saying at least one field to edit must be provided.
+
+2. Editing a completed job
+
+ 1. Prerequisites: List all completed jobs using the `listJob -c` command. Shows list of completed jobs in the main panel.
+ Must have at least 1 completed job in the list for below test cases to work.
+
+ 2. Test case: `editJob 1 d/Completed repair for CPU`
+
Expected:
+ - Job at index 1 in the currently displayed job list, should have its description updated accordingly.
+ - Details of job edited shown in status message.
+
+### Deleting a job
+
+1. Deleting an in-progress job
+
+ 1. Prerequisites: List all in-progress jobs using the `listJob` command. Shows list of in-progress jobs in the main panel.
+ Must have at least 1 in-progress job in the list for below test cases to work.
+
+ 2. Test case: `deleteJob 1`
+
Expected:
+ - Job at index 1 in the currently displayed job list is deleted.
+ - Details of deleted job shown in status message.
+
+ 3. Test case: `deleteJob -1`
+
Expected:
+ - No jobs are deleted.
+ - Invalid command format error shown.
+
+2. Deleting a completed job.
+
+ 1. Prerequisites: List all completed jobs using the `listJob -c` command. Shows list of completed jobs in the main panel.
+ Must have at least 1 completed job in the list for below test cases to work.
+
+ 2. Test case: `deleteJob 1`
+
Expected:
+ - Job at index 1 in the currently displayed job list is deleted.
+ - Details of deleted job shown in status message.
+ - Job list should continue to show list of all completed jobs. (Or an empty list if the only completed job was deleted)
+
+### Finding jobs
+
+1. Test case: `findJob CPU`
+
Expected:
+ - Matching jobs (both in-progress and completed) are shown on list.
+ - Number of jobs found shown in status message.
+
+### Marking job as complete
+
+1. Marking an in-progress job as complete.
+
+ 1. Prerequisites: Prerequisites: List all in-progress jobs using the `listJob` command. Shows list of in-progress jobs in the main panel.
+ Must have at least 1 in-progress job in the list for below test cases to work.
+
+ 2. Test case: `completeJob 1 10/01/2022`
+
Expected:
+ - Job at index 1 in the currently displayed job list will be marked as complete, and will disappear from the current job
+ list displaying all in-progress jobs.
+ - Details of completed job shown in status message.
+
+2. Marking a completed job as complete.
+
+ 1. Prerequisites: List all completed jobs using the `listJob -c` command. Shows list of completed jobs in the main panel.
+ Must have at least 1 completed job in the list for below test cases to work.
+
+ 2. Test case: `completeJob 1`
+
Expected:
+ - Job at index 1 in the currently displayed job list remains unchanged.
+ - Error message informing user that the job has already been completed is shown.
+
+### Revert completion status of a previously complete job
+
+1. Revert completion status of an in-progress job.
+
+ 1. Prerequisites: Prerequisites: List all in-progress jobs using the `listJob` command. Shows list of in-progress jobs in the main panel.
+ Must have at least 1 in-progress job in the list for below test cases to work.
+
+ 2. Test case: `undoCompleteJob 1`
+
Expected:
+ - Job at index 1 in the currently displayed job list remains unchanged.
+ - Error message informing user that the job has not been completed yet is shown.
+
+2. Revert completion status of a completed job.
+
+ 1. Prerequisites: List all completed jobs using the `listJob -c` command. Shows list of completed jobs in the main panel.
+ Must have at least 1 completed job in the list for below test cases to work.
+
+ 2. Test case: `undoCompleteJob 1`
+
Expected:
+ - Completion status of Job at index 1 in the currently displayed job list reverted.
+ - Details of job whose completion status was reverted is shown.
+
+
+### Listing jobs
+
+1. Test case: `listJob`
+
Expected:
+ - Main panel will list all in-progress jobs.
+
+2. Test case: `listJob -a`
+
Expected:
+ - Main panel will list all jobs (completed and in-progress).
+
+3. Test case: `listJob -c`
+
Expected:
+ - Main panel will list all completed jobs.
+
+### Retrieving history command
+
+1. Retrieving all history commands in a list.
+
+ 1. Test case: `history`
+
Expected:
+ - All previously entered commands are shown in side panel.
+
+2. Retrieving the most recent history command.
+
+ 1. Prerequisites: At least two commands are entered i.e. `listProduct`, `anything`, etc.
+
+ 2. Test case: "Press up arrow key"
+
Expected:
+ - The most recent history command will appear in command box.
+
+ 3. Test case: "Press up arrow key twice"
+
Expected:
+ - The command before the last one will appear in command box.
+
+ 4. Test case: "Press up arrow key twice and then press down arrow key"
+
Expected:
+ - The last entered command will appear in command box.
+
+### Printing out monthly job report
+
+1. Printing out monthly job report while report window is not shown.
+
+ 1. Prerequisites: At least one contact is added to MyCRM
+
i.e. `addContact n/Sans c/83921823 e/Sans@gmail.com a/Maxwell Chambers 32 Maxwell Rd`
+
two jobs are added to MyCRM
+
i.e. `addJob d/Change CPU fee/$50 by/10/11/2021 c/1 p/2`, `addJob d/Change GPU fee/$55 by/11/11/2021 c/1 p/1`
+
and two products are added to MyCRM.
+
i.e. `addProduct n/Asus DUAL-GTX1060-O6G t/GPU m/Asus`, `addProduct n/Intel i5-10400F t/CPU m/Intel d/2.90GHz`
+
Then marking one job as completed
+
i.e. `completeJob 1`
+
+ 2. Test case: `printReport`
+
Expected:
+ - The report window will pop up.
+ - The report window will show the monthly completed jobs in a list and a bar graph indicating the monthly revenue for last four months of this year and last year.
+
+ 3. Test case: `printReport -i`
+
Expected;
+ - The report window will pop up.
+ - The report window will show the incompleted jobs received in this month and a bar graph similar to last test case.
+
+ 4. Test case: `printReport -p`
+
Expected:
+ - The report window will pop up.
+ - The report window will show the top three popular product received in this month and a bar graph similar to last test case.
+
+2. Printing out monthly job report while report window is shown but minimized.
+
+ 1.Prerequisites: Similar to (1) but minimize the report window.
+
+ 2. Test case similar to (1)
+
Expected:
+ - Similar to (1)
+ - The report window is focused.
+
+### Exporting monthly job report
+
+1. Exporting monthly job report while report window is opened.
+
+ 1. Prerequisites: Open report window i.e. `printReport`.
+
+ 2. Test case: "Click on the Print button"
+
Expected:
+ - MyCRM will show a page setup page and print page (on WindowsOS).
+ - MyCRM will show a print page (on MacOS).
+
+2. Exporting monthly job report while report window is not opened.
+
+ 1. Prerequisites: Not open report window beforehand.
+
+ 1. Test case: `exportReport`
+
Expected:
+ - Similar to (1),
+
+:information_source: **Note:** *Print* button might not always work for MacOS users.
+
+
-1. _{ more test cases … }_
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
index 275445bd551..3d62bbe3982 100644
--- a/docs/SettingUp.md
+++ b/docs/SettingUp.md
@@ -23,7 +23,7 @@ If you plan to use Intellij IDEA (highly recommended):
1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
:exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project.
1. **Verify the setup**:
- 1. Run the `seedu.address.Main` and try a few commands.
+ 1. Run the `seedu.mycrm.Main` and try a few commands.
1. [Run the tests](Testing.md) to ensure they all pass.
--------------------------------------------------------------------------------------------------------------------
diff --git a/docs/Testing.md b/docs/Testing.md
index 8a99e82438a..961ddf9cd33 100644
--- a/docs/Testing.md
+++ b/docs/Testing.md
@@ -29,8 +29,8 @@ There are two ways to run tests.
This project has three types of tests:
1. *Unit tests* targeting the lowest level methods/classes.
- e.g. `seedu.address.commons.StringUtilTest`
+ e.g. `seedu.mycrm.commons.StringUtilTest`
1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
- e.g. `seedu.address.storage.StorageManagerTest`
+ e.g. `seedu.mycrm.storage.StorageManagerTest`
1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
- e.g. `seedu.address.logic.LogicManagerTest`
+ e.g. `seedu.mycrm.logic.LogicManagerTest`
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 3716f3ca8a4..14071e18e17 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,41 +3,51 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+MyCRM is a **desktop application for managing client contacts, repair job statuses, and product information that has
+been optimised for use via a Command Line Interface (CLI)** while maintaining the benefits of a Graphical User Interface
+(GUI). If you type quickly, MyCRM can complete customer relationship management tasks faster than traditional GUI
+applications.
+
+**Target Audience:** Tech savvy computer repair shop technician. Owns a business repairing computers and laptops,
+actively servicing multiple clients and answering their queries. Services a wide range of models and deals with both
+hardware and software issues. Also has multiple repair-phases which have to be updated to clients.
+
* Table of Contents
{:toc}
--------------------------------------------------------------------------------------------------------------------
-
+
## Quick start
1. Ensure you have Java `11` or above installed in your Computer.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+2. Download the latest `MyCRM.jar` from [here](https://github.com/AY2122S1-CS2103-T14-3/tp/releases).
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+3. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
-1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+4. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the
+ app contains some sample data.
![Ui](images/Ui.png)
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+5. Type the command in the command box and press Enter to execute it.
Some example commands you can try:
- * **`list`** : Lists all contacts.
+ * **`listContact `** : Lists all contacts.
- * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+ * **`addContact `**`n/John Doe c/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact
+ named `John Doe` to the CRM.
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list.
+ * **`deleteContact `**`1` : Deletes the 1st contact shown in the current list.
- * **`clear`** : Deletes all contacts.
+ * **`clear`** : Deletes all contacts, jobs and products in MyCRM.
* **`exit`** : Exits the app.
-1. Refer to the [Features](#features) below for details of each command.
+6. Refer to the [Features](#features) below for details of each command.
--------------------------------------------------------------------------------------------------------------------
-
+
## Features
@@ -54,139 +64,636 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
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. if the command specifies `n/NAME c/PHONE_NUMBER`, `c/PHONE_NUMBER n/NAME` is also acceptable.
-* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken.
+* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence
+ of the parameter will be taken.
+ e.g. if you specify `c/12341234 c/56785678`, only `c/56785678` will be taken.
-* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`.
+* Extraneous parameters for commands that do not take in parameters (such as `listJob`, `listProduct`, and `exit`
+ ) will be ignored.
+ e.g. if the command specifies `listJob 123`, it will be interpreted as `listJob`.
-### Viewing help : `help`
+### Adding a job: `addJob`
+
+Adds a new repair job to the CRM.
+
+Format: `addJob d/DESCRIPTION by/EXPECTED_COMPLETION_DATE fee/FEE [recv/RECIEVED_DATE] [c/CONTACT_INDEX] [p/PRODUCT_INDEX]`
+Format of special subcommands: `select INDEX` and `abort` (details on subcommand usage below)
+
+* Creates a new repair job.
+* Links the contact and product that correspond to `CONTACT_INDEX` and `PRODUCT_INDEX` (in the respective contact
+ and product list) to the job.
+* `EXPECTED_COMPLETION_DATE` refers to the date by which the repair expected to be completed.
+* `FEE` refers to the repair fee charged to the client.
+* `RECIEVED_DATE` refers to the date the repair job request was received from the client. If not provided, it is by default,
+ set to the date the job is created in MyCRM.
+* Both product and contact are compulsory attributes of job. If they are not provided
+ in the form of an index in the above command, the job is not immediately added.
+ * Instead, in such a case, the user will be asked for info on the missing contact or product (or both).
+ * User can choose to create a new contact/product and immediately assign it to the job currently being created
+ via the `addContact` and `addProduct` commands.
+ * Or the user can issue a command `select INDEX` to select an item from its displayed list.
+ * While the user is being asked for a contact/product only selected commands will be allowed
+ (namely the list, find and add commands for either product or contact).
+ * The user can choose to stop this operation and not add any new job by issuing a `abort` command.
+ * Note: The user is asked for the contact and product one after the other. i.e If asked for the contact first
+ user cannot select or create a product instead.
+ * Note: If the operation is stopped via the `abort` command, the job will not be added but any new products/contacts
+ created through the `addContact` and `addProduct` commands will still be added.
-Shows a message explaning how to access the help page.
+Examples:
-![help message](images/helpMessage.png)
+* To add a job with a pre-existing contact and product
+* Either the command `addJob d/Change CPU fee/$50 by/10/11/2021 c/1 p/1` can be issued OR
+* The following sequence of commands can be issued:
+ * `addJob d/Change CPU fee/$50 by/10/11/2021`
+ * `select 1` (to select contact)
+ * `select 1` (to select product)
+
+
+
+* To add a job with a new contact and product
+* Issue the following sequence of commands:
+ * `addJob d/Change CPU fee/$50 by/10/11/2021`
+ * `addContact n/Jack Ryan c/94678954 a/Blk 65 Tampines Ave 1 e/jryan@gmail.com`
+ * `addProduct n/Asus DUAL-GTX1060-O6G t/GPU m/Asus d/DisplayPort, HDMI`
+
+
+
+### Editing a job: `editJob`
+
+Edits an existing repair job to the CRM.
+
+Format: `editJob INDEX [d/DESCRIPTION] [by/EXPECTED_COMPLETION_DATE] [fee/FEE] [recv/RECIEVED_DATE] [c/CONTACT_INDEX] [p/PRODUCT_INDEX]`
+Format of special subcommands: `select INDEX` and `abort` (details on subcommand usage below)
+
+* Edits the repair job at the specified `INDEX`
+* `INDEX` refers to the index of the repair job as shown in the repair job listing
+* `INDEX` must be a positive integer(1,2,3…)
+* It is possible to not indicate the `CONTACT_INDEX` or `PRODUCT_INDEX`. i.e A command like `editJob INDEX c/ p/` is valid.
+ * In such a case the user will be asked for info which product or contact (or both)
+ they now want to assign to the job.
+ * User can choose to create new contact/product and immediately assign it via
+ the `addContact` and `addProduct` commands.
+ * Or the user can issue a command `select INDEX` to select an item from the displayed list.
+ * While the user is being asked for a contact/product only selected commands will be allowed
+ (namely the list, find and add commands for either product or contact).
+ * The user can choose to stop this operation and not edit the job by issuing a `abort` command.
+ * Note: The user is asked for the contact and product one after the other. i.e If asked for the contact first
+ user cannot select or create a product instead.
+ * Note: If the operation is stopped via the `abort` command, the job will not be edited at all.
+ However, any new products/contacts created through the `addContact` and `addProduct` commands will still be added.
-Format: `help`
+Examples:
+* To edit a job and reassign to it other pre-existing contact and product
+* Either the command `editJob 1 c/1 p/2` can be issued OR
+* The following sequence of commands can be issued:
+ * `editJob 1 c/ p/`
+ * `select 1` (to select contact)
+ * `select 2` (to select product)
-### Adding a person: `add`
+
-Adds a person to the address book.
+* To add a job with new a contact and product
+* Issue the following sequence of commands:
+ * `editJob 1 c/ p/`
+ * `addContact n/James Bond c/94678007 a/Blk 61 Yishun Ave 1 e/bond@gmail.com`
+ * `addProduct n/SAMSUNG 980 PRO 1TB SSD t/Hard disk m/SAMSUNG d/SATA`
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+
+
+### Listing all jobs: `listJob`
-:bulb: **Tip:**
-A person can have any number of tags (including 0)
-
+Shows a list of all in-progress repair jobs in the CRM.
+
+Format: `listJob [-a] [-c]`
+
+* To show a list of all jobs, regardless of completion status the command `listJob -a` can be issued
+* To show a list of all completed jobs the command `listJob -c` can be issued
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`
+* `listJob`
+
+
+
+* `listJob -c`
+
+
+
+* `listJob -a`
+
+
+
+### Find Job: `findJob`
+
+Find jobs whose description, contact or product contain certain keywords.
+
+Format: `findJob [MORE_KEYWORDS]...`
+
+* The search is case-insensitive. e.g. `intel` will match `Intel`.
+* Only full words will be matched. e.g. `Inte` will not match `Intel`.
+* Jobs matching at least one keyword will be returned.
+
+Example:
+
+* `findJob charlotte intel`
+
+
+
+### Marking job as complete: `completeJob`
-Shows a list of all persons in the address book.
+Marks a repair job as complete
-Format: `list`
+Format: `completeJob INDEX [COMPLETION_DATE]`
-### Editing a person : `edit`
+* Marks the repair job at the specified `INDEX` as complete
+* By default `listJob` only shows jobs that are yet to be completed. As such marking the job as complete will cause
+ it to disappear from the current job list. User can issue the command `listJob -c` to view the list of completed jobs.
+* `INDEX` refers to the index of the repair job as shown in the repair job listing
+* `INDEX` must be a positive integer(1,2,3…)
+* `COMPLETION_DATE` is set to the current date if it is not provided
-Edits an existing person in the address book.
+Example:
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+* Given one in-progress job
+
+
+
+* `completeJob 1` - causes the job to disappear from the current job list
+
+
+
+* If `listJob -c` is called, it can be seen that the job was successfully completed
+
+
+
+### Revert the completion status of a previously complete job: `undoCompleteJob`
+
+Reverts the status of a previously completed job back to in-progress.
+
+Format: `undoCompleteJob INDEX`
+
+* Marks the repair job at the specified `INDEX` as complete
+* User should call `listJob -c` to view all completed jobs before calling this command
+* `INDEX` must refer to a completed job. It will not work on a currently in-progress job
+* `INDEX` refers to the index of the repair job as shown in the repair job listing
+* `INDEX` must be a positive integer(1,2,3…)
+
+Example:
+
+* Given one completed job
+
+
+
+* If `undoCompleteJob 1` is called, it can be seen that the job's status was reverted to in-progress
+
+
+
+### Deleting a job: `deleteJob`
+
+Delete the specified repair job from the CRM
+
+Format: `deleteJob INDEX`
+
+* Deletes the repair job at the specified `INDEX`
+* `INDEX` refers to the index of the repair job as shown in the repair job listing
+* `INDEX` must be a positive integer(1,2,3…)
+
+### Adding a contact: `addContact`
+
+Add a new contact info of a client into the CRM.
+
+Format: `addContact n/CLIENT_NAME [c/CONTACT_NUMBER] [e/EMAIL] [a/ADDRESS] [t/tags]`
+
+* Creates a new contact info of a client.
+* At least one field of `c/CONTACT_NUMBER` `e/EMAIL` `a/ADDRESS`should exist even though they are considered optional fields.
+
+ i.e. `addContact n/CLIENT_NAME` is not allowed.
+
+ `addContact n/CLIENT_NAME e/EMAIL`, `addContact n/CLIENT_NAME c/CONTACT_NUMBER` commands like these are valid.
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
-* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
-### Locating persons by name: `find`
+* `addContact n/Frisk c/93487234 e/Frisk@gmail.com a/Laptop Factory Outlet Bugis Junction`
+* `addContact n/Sans c/83921823 e/Sans@gmail.com a/Maxwell Chambers 32 Maxwell Rd`
+
+
+
+### Deleting a contact: `deleteContact`
+
+Deletes the specified contact from the CRM
+
+Format: `deleteContact INDEX`
-Finds persons whose names contain any of the given keywords.
+* Deletes the contact at the specified `INDEX`
+* `INDEX` refers to the index of the contact as shown in the contact listing
+* `INDEX` must be a positive integer(1,2,3…)
+* If this contact is link to a job, it can not be deleted unless the linked job is deleted.
-Format: `find KEYWORD [MORE_KEYWORDS]`
+Example:
-* 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`
+`deleteContact 3`
+
+
+
+### Editing a contact: `editContact`
+
+Edits the specified contact from the CRM
+
+Format: `editContact INDEX [n/NAME] [c/PHONE] [e/EMAIL] [a/ADDRESS]`
+
+* At least one field of name, phone, email or address has to provide in order to
+ change a contact's info.
+* After invoking `editContact ...` command, the job linked to this contact will also
+ update.
+
+
+Example:
+
+`editContact 1 a/Jurong West Street 42`
+
+
+
+### Finding a contact: `findContact `
+
+Find certain contact with keyword specified.
+
+Format: `findContact [MORE_KEYWORDS]... `
+
+* User must provide at least one keyword of a contact.
+
+Example:
+
+`findContact Frisk Sans`
+
+
+
+
+### Hiding a contact: `hideContact`
+
+Hide certain contact with INDEX specified.
+
+Format: `hideContact INDEX`
+
+* `hideContact` will add a tag `hidden` to those being hidden.
+* Cannot invoke `hideContact` **again** to those being hidden.
+* Contacts hidden does not display in MyCRM unless `listContact -a` is invoked.
+* So far hiding a specific contact will not affect job card.
+
+Example:
+
+`hideContact 1`
+
+
+
+### Undoing hiding a contact: `undoHideContact`
+
+Undo a previous `hideContact` command to certain contact with INDEX specified.
+
+Format: `undoHideContact INDEX`
+
+* `listContact -a` must be called in order to see hidden contacts.
+* `undoHideContact` will delete `hidden` tag to the hidden contact.
+* Cannot invoke `undoHideContact` to visible contacts.
+
+Example:
+
+`undoHideContact 1`
+
+
+
+
+### Listing all contacts: `listContact`
+
+Show a list of all contact info in the CRM.
+
+Format: `listContact` or `listContact -a`
+
+* Normally, `listContact` will only list contacts not being hidden.
+* If `listContact -a` is invoked, all contacts including hidden ones will be listed.
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`
+* `listContact`
+
+
+
+* `listContact -a`
+
+
+
-Deletes the specified person from the address book.
+### Adding a product: `addProduct`
-Format: `delete INDEX`
+Adds a new product to the CRM.
-* 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: `addProduct n/NAME [t/TYPE] [m/MANUFACTURER] [d/DESCRIPTION]`
+
+* `NAME` is a compulsory field. It must be non-empty.
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.
-### Clearing all entries : `clear`
+* `addProduct n/Asus DUAL-GTX1060-O6G t/GPU m/Asus`
+* `addProduct n/Intel i5-10400F t/CPU m/Intel d/2.90GHz`
-Clears all entries from the address book.
+
-Format: `clear`
+### Listing all products: `listProduct`
+
+Shows a list of all products in the CRM.
+
+Format: `listProduct`
+
+### Deleting a product: `deleteProduct`
+
+Deletes the specified product from the CRM.
+
+Format: `deleteProduct INDEX`
+
+* Deletes the product at the specified `INDEX`.
+* `INDEX` refers to the index of the product as shown in the product listing.
+* `INDEX` must be a positive integer(1,2,3…).
+* A product cannot be deleted if it is linked to one or more jobs.
+
+### Editing a product: `editProduct`
+
+Edits an existing product in the CRM.
+
+Format: `editProduct INDEX [n/NAME] [t/TYPE] [m/MANUFACTURER] [d/DESCRIPTION]`
+
+* Edits the product at the specified `INDEX`.
+* The index refers to the index number shown in the displayed product list.
+* `INDEX` must be a positive integer(1,2,3…).
+* At least one of the optional fields must be provided.
+* No change is made if a field is empty. e.g.
+ `editProduct 1 m/Asus t/` will not change the product's `Type` field.
+* Changes in product fields will be updated in job list *automatically*.
+
+Example:
+
+* `editProduct 3 d/Video output interface: DisplayPort, HDMI` edits the description of the 3rd product to be
+ `Video output interface: DisplayPort, HDMI`.
+
+
+
+### Locating products by name: `findProduct`
+
+Finds products whose names contain certain keywords.
+
+Format: `findProduct [MORE_KEYWORDS]...`
+
+* The search is case-insensitive. e.g. `asus` will match `Asus`.
+* Only full words will be matched. e.g. `Asu` will not match `Asus`.
+* Products matching at least one keyword will be returned.
+
+Example:
+
+* `findProduct asus`
+
+
+
+### Send mail: `mail`
+
+Constructs an email to send to a customer of a specified job. This command also generates a `mailto:` hyperlink to
+mail the email to a customer of a specified job.
+
+Format: `mail j/JOB_INDEX t/TEMPLATE_INDEX`
+
+* Constructs a new email with template content and contact details of the job at the specified `JOB_INDEX` and
+ `TEMPLATE_INDEX`.
+* `JOB_INDEX` refers to the index of the job shown in the repair job listing.
+* `JOB_INDEX` must be a positive integer (1,2,3…).
+* `TEMPLATE_INDEX` refers to the index of the template as shown in the template listing.
+* `TEMPLATE_INDEX` must be a positive integer (1,2,3…).
+* `Job` selected must have an email address, non-empty.
+
+Examples:
+
+* `listJob` and `listTemplate` followed by` mail j/1 t/1` constructs an email to the 2nd job’s customer with the 2nd
+ email template and `mailto:` hyperlink.
+
+
+
+
+
+### Adding mail template: `addTemplate`
+
+Adds a new email template into the CRM.
+
+Format: `addTemplate s/SUBJECT b/BODY`
+
+* `SUBJECT` and `BODY` field must be non-empty.
+* `SUBJECT` only accepts alphanumeric values and spaces in between.
+ * i.e. Special characters are not allowed.
+ * Such as `s/He@der 3!` are not allowed.
+* `BODY` accepts any string value (alphanumeric and special characters).
+* `BODY` processes special string `\n` as newline for constructing email.
+ * This is only displayed on `mail` command
+
+Examples:
+
+* `addTemplate s/Issue Has Occurred b/Attention:\nYour product has encountered an issue` adds a new Template with
+ subject "Issue Has Occurred" and body "Attention:\nYour product has encountered an issue".
+* `addTemplate s/Your order is confirmed b/Your order is confirmed! Thank you for ordering from XXX` adds a new
+ Template with subject "our order is confirmed" and body "Your order is confirmed! Thank you for ordering from XXX".
+
+
+
+### Listing all templates: `listTemplate`
+
+Shows a list of all templates in the CRM.
+
+Format: `listTemplate`
+
+
+
+### Editing mail template: `editTemplate`
+
+Edits the specified template from the CRM.
+
+Format: `editTemplate INDEX [s/SUBJECT] [b/BODY]`
+
+* At least one optional edit field must be provided
+* `SUBJECT` only accepts alphanumeric values and spaces in between.
+* `BODY` accepts any string value (alphanumeric and special characters).
+
+Edits the template at the specified `INDEX`
+
+* `INDEX` refers to the index of the template as shown in the template listing
+* `INDEX` must be a positive integer(1,2,3…).
+
+Examples:
+
+* `listTemplate` followed by `editTemplate 4 b/We’re excited for you to receive your order` edits the 4th email
+ template in the CRM, overriding the 4th email template's body with the new input.
+
+
+
+### Finding mail template: `findTemplate`
+
+Find certain template(s) with keyword specified.
+
+Format: `findTemplate [MORE_KEYWORDS]... `
+
+* User must provide at least one keyword of a template.
+* `MORE_KEYWORDS` searches for `Subject` title
+* `MORE_KEYWORDS` are case-insensitive
+* `MORE_KEYWORDS` searched are whole words
+
+Example:
+
+* `findTemplate Order`
+
+
+
+### Deleting mail template: `deleteTemplate`
+
+Deletes the specified template from the CRM.
+
+Format: `deleteTemplate INDEX`
+
+Deletes the template at the specified `INDEX`
+* `INDEX` refers to the index of the template as shown in the template listing.
+* `INDEX` must be a positive integer(1,2,3…).
+
+Examples:
+
+* `listTemplate` followed by `deleteTemplate 4` deletes the 4th email template in the CRM.
+
+
+
+### Retrieve previous command: `history`
+
+Retrieves the previously entered command
+
+Format: Press "Up arrow key" / `history`
+
+* ‘Press Up arrow key’ on the keyboard to gain the most recent command in the CRM.
+* `history` will list all history commands in the CRM
+
+Examples:
+
+* `history`(Assume that `listJob` and `listContact` have been entered before running `history`):
+
+
+
+### Clear history command data: `clearHistory`
+
+Clears all historical data of user input
+
+Format: `clearHistory`
+
+### Viewing user guide: `help`
+
+Shows a message explaining how to access the help page and a hyperlink to it.
+
+Format: `help`
+
+
### Exiting the program : `exit`
-Exits the program.
+Ends MyCRM and exits the programme.
Format: `exit`
-### Saving the data
+### Clearing data: `clear`
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+Clears current data in the CRM. Empties CRM data.
-### Editing the data file
+Format: `clear`
-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.
+
-: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.
-
+### Changing the theme of user interface `theme`
+
+Changes the theme of user interface.
+
+Format: `theme THEME_NAME`
-### Archiving data files `[coming in v2.0]`
+* There are 2 available Ui themes of the CRM. Their names are `dark` and `light`.
+* `THEME_NAME` is case-insensitive.
-_Details coming soon ..._
+Example: `theme light`
+
+
+
+### Printing monthly report `printReport`
+
+Prints a report of all jobs within current month.
+
+Format: `printReport [-i] [-p]`
+
+* To show a report window with monthly in-progress jobs list, the command `printReport -i` can be issued
+* To show a report window with monthly top-three products list, the command `printReport -p` can be issued
+* MyCRM will show a report window with monthly completed job list by default
+
+
+
+### Export monthly report
+
+Exports a report of all jobs within current month to printer.
+
+Format: Click on "Print" / `exportReport`
+
+
+
+
+ (for Windows OS)
+
+
+
+ (for Mac OS)
--------------------------------------------------------------------------------------------------------------------
## 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 MyCRM home folder.
--------------------------------------------------------------------------------------------------------------------
-
+
## Command summary
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX`
e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+Action | Format, Examples
+--------------------|------------------
+**Add Job** | `addJob d/DESCRIPTION by/EXPECTED_COMPLETION_DATE fee/FEE [recv/RECIEVED_DATE] [c/CONTACT_INDEX] [p/PRODUCT_INDEX]`
e.g.,`addJob d/CPU replacement needed c/1 p/1 by/15/09/2021 fee/30.00`
+**Edit Job** | `editJob INDEX [d/DESCRIPTION] [by/EXPECTED_COMPLETION_DATE] [fee/FEE] [recv/RECIEVED_DATE] [c/CONTACT_INDEX] [p/PRODUCT_INDEX]`
e.g., `editJob 1 fee/50.00 c/2 p/3`
+**List Job** | `listJob`
+**Find Job** | `findJob [MORE_KEYWORDS]... `
e.g., `findJob charlotte intel`
+**Complete Job** | `completeJob INDEX [COMPLETION_DATE]`
e.g., `completeJob 1`
+**Undo Complete Job** | `undoCompleteJob INDEX`
e.g., `undoCompleteJob 1`
+**Delete Job** | `deleteJob INDEX`
e.g., `deleteJob 2`
+**Select** | `select INDEX`
e.g.,`select 1`
+**Abort** | `abort`
+**Add Contact** | `addContact n/CLIENT_NAME [c/CONTACT_NUMBER] [e/EMAIL] [a/ADDRESS] [t/tag]`
e.g., `addContact n/Frisk c/93487234 e/Frisk@gmail.com a/Laptop Factory Outlet Bugis Junction`
+**Edit Contact** |`editContact INDEX [n/NAME] [c/PHONE] [e/EMAIL] [a/ADDRESS] `
e.g., `EditContact 1 n/Dante`
+**List Contact** | `listContact` `listContact -a`
+**Find Contact** |`findContact [MORE_KEYWORDS]... `
e.g., `findContact Sans`
+**Hide Contact** |`hideContact INDEX `
e.g., `hideContact 1`
+**Undo Hide Contact** |`undoHideContact INDEX... `
e.g., `undoHideContact 1`
+**Delete Contact** | `deleteContact INDEX`
e.g., `deleteContact 4`
+**Add Product** | `addProduct n/NAME [t/TYPE] [m/MANUFACTURER] [d/DESCRIPTION]`
e.g., `addProduct n/Asus DUAL-GTX1060-O6G t/GPU m/Asus`
+**List Product** | `listProduct`
+**Delete Product** | `deleteProduct INDEX`
e.g., `deleteProduct 4`
+**Edit Product** | `editProduct INDEX [n/NAME] [t/TYPE] [m/MANUFACTURER] [d/DESCRIPTION]`
e.g., `editProduct 2 d/Video output interface: DisplayPort, HDMI`
+**Find Product** | `findProduct [MORE_KEYWORDS]...`
e.g., `findProduct asus`
+**Mail** | `mail j/JOB_INDEX t/TEMPLATE_INDEX`
e.g., `mail j/3 t/1`
+**Add Template** | `addTemplate s/SUBJECT b/BODY`
e.g., `addTemplate s/Repair In Progress b/Your product is current;y being repaired`
+**List Templates** | `listTemplate`
+**Find Templates** |`findTemplate [MORE_KEYWORDS]... `
e.g., `findTemplates complete`
+**Edit Templates** | `editTemplate INDEX [s/SUBJECT] [b/SUBJECT]`
e.g., `editTemplate 2 s/Your immediate attention`
+**Delete Template** | `deleteTemplate INDEX`
e.g., `deleteTemplate 4`
+**Retrieve Previous Command** | `history`, "Press Up arrow key"
+**Exit** | `exit`
+**Change Theme** | `theme THEME_NAME`
e.g., `theme light`
+**Print Monthly Report** | `printReport`
+**Export Monthly Report** | `exportReport`
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..3e0714c029f 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,5 +1,5 @@
-title: "AB-3"
-theme: minima
+title: "MyCRM"
+theme: jekyll-theme-cayman
header_pages:
- UserGuide.md
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2122S1-CS2103-T14-3/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..a305363b02e 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: "MyCRM";
font-size: 32px;
}
}
diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml
index ef81d18c337..a5901d151ae 100644
--- a/docs/diagrams/ArchitectureSequenceDiagram.puml
+++ b/docs/diagrams/ArchitectureSequenceDiagram.puml
@@ -7,19 +7,19 @@ 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 : "deleteContact 1"
activate ui UI_COLOR
-ui -[UI_COLOR]> logic : execute("delete 1")
+ui -[UI_COLOR]> logic : execute("deleteContact 1")
activate logic LOGIC_COLOR
-logic -[LOGIC_COLOR]> model : deletePerson(p)
+logic -[LOGIC_COLOR]> model : deleteContact(contact)
activate model MODEL_COLOR
model -[MODEL_COLOR]-> logic
deactivate model
-logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook)
+logic -[LOGIC_COLOR]> storage : saveMyCrm(myCrm)
activate storage STORAGE_COLOR
storage -[STORAGE_COLOR]> storage : Save to file
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
index 5731f9cbaa1..4f2f0e361ba 100644
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ b/docs/diagrams/BetterModelClassDiagram.puml
@@ -4,18 +4,20 @@ skinparam arrowThickness 1.1
skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
-AddressBook *-right-> "1" UniquePersonList
-AddressBook *-right-> "1" UniqueTagList
-UniqueTagList -[hidden]down- UniquePersonList
-UniqueTagList -[hidden]down- UniquePersonList
+MyCrm *-right-> "1" UniqueContactList
+MyCrm *-right-> "1" UniqueTagList
+UniqueTagList -[hidden]down- UniqueContactList
+UniqueTagList -[hidden]down- UniqueContactList
UniqueTagList *-right-> "*" Tag
-UniquePersonList -right-> Person
+UniqueContactList -right-> Contact
+
+Contact -up-> "*" Tag
+
+Contact *--> Name
+Contact *--> Phone
+Contact *--> Email
+Contact *--> Address
-Person -up-> "*" Tag
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
index 1dc2311b245..b9a29238e8c 100644
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ b/docs/diagrams/DeleteSequenceDiagram.puml
@@ -3,7 +3,7 @@
box Logic LOGIC_COLOR_T1
participant ":LogicManager" as LogicManager LOGIC_COLOR
-participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR
participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR
participant ":CommandResult" as CommandResult LOGIC_COLOR
@@ -16,17 +16,17 @@ end box
[-> LogicManager : execute("delete 1")
activate LogicManager
-LogicManager -> AddressBookParser : parseCommand("delete 1")
-activate AddressBookParser
+LogicManager -> MyCrmParser : parseCommand("delete 1")
+activate MyCrmParser
create DeleteCommandParser
-AddressBookParser -> DeleteCommandParser
+MyCrmParser -> DeleteCommandParser
activate DeleteCommandParser
-DeleteCommandParser --> AddressBookParser
+DeleteCommandParser --> MyCrmParser
deactivate DeleteCommandParser
-AddressBookParser -> DeleteCommandParser : parse("1")
+MyCrmParser -> DeleteCommandParser : parse("1")
activate DeleteCommandParser
create DeleteCommand
@@ -36,14 +36,14 @@ activate DeleteCommand
DeleteCommand --> DeleteCommandParser : d
deactivate DeleteCommand
-DeleteCommandParser --> AddressBookParser : d
+DeleteCommandParser --> MyCrmParser : d
deactivate DeleteCommandParser
'Hidden arrow to position the destroy marker below the end of the activation bar.
-DeleteCommandParser -[hidden]-> AddressBookParser
+DeleteCommandParser -[hidden]-> MyCrmParser
destroy DeleteCommandParser
-AddressBookParser --> LogicManager : d
-deactivate AddressBookParser
+MyCrmParser --> LogicManager : d
+deactivate MyCrmParser
LogicManager -> DeleteCommand : execute()
activate DeleteCommand
diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml
index 6d14b17b361..a823fa8b4fb 100644
--- a/docs/diagrams/LogicClassDiagram.puml
+++ b/docs/diagrams/LogicClassDiagram.puml
@@ -6,11 +6,11 @@ skinparam classBackgroundColor LOGIC_COLOR
package Logic {
-Class AddressBookParser
+Class MyCrmParser
Class XYZCommand
Class CommandResult
Class "{abstract}\nCommand" as Command
-
+Class StateManager
Interface Logic <>
Class LogicManager
@@ -27,12 +27,17 @@ Class HiddenOutside #FFFFFF
HiddenOutside ..> Logic
LogicManager .right.|> Logic
-LogicManager -right->"1" AddressBookParser
-AddressBookParser ..> XYZCommand : creates >
+LogicManager -right->"1" MyCrmParser
+MyCrmParser ..> XYZCommand : creates >
XYZCommand -up-|> Command
LogicManager .left.> Command : executes >
+StateManager .up.> CommandResult : produces >
+LogicManager --> StateManager
+Command .up.> StateManager : intercepts <
+StateManager .right.> Model
+
LogicManager --> Model
LogicManager --> Storage
Storage --[hidden] Model
@@ -43,4 +48,5 @@ note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc
Logic ..> CommandResult
LogicManager .down.> CommandResult
Command .up.> CommandResult : produces >
+
@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 1122257bd9a..1ea2e1d94bd 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -5,50 +5,47 @@ skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
Package Model <>{
-Interface ReadOnlyAddressBook <>
+Interface ReadOnlyMyCrm <>
Interface ReadOnlyUserPrefs <>
Interface Model <>
-Class AddressBook
-Class ReadOnlyAddressBook
+Class MyCrm
+Class ReadOnlyMyCrm
Class Model
Class ModelManager
Class UserPrefs
Class ReadOnlyUserPrefs
-
-Class UniquePersonList
-Class Person
-Class Address
-Class Email
-Class Name
-Class Phone
-Class Tag
-
+package Entities {
+package Contact #F4F6F6{}
+package Product #F4F6F6{}
+package Template #F4F6F6{}
+package Job #F4F6F6{}
+package Mail #F4F6F6{}
+}
}
Class HiddenOutside #FFFFFF
HiddenOutside ..> Model
-AddressBook .up.|> ReadOnlyAddressBook
+MyCrm .up.|> ReadOnlyMyCrm
ModelManager .up.|> Model
Model .right.> ReadOnlyUserPrefs
-Model .left.> ReadOnlyAddressBook
-ModelManager -left-> "1" AddressBook
+Model .left.> ReadOnlyMyCrm
+ModelManager -left-> "1" MyCrm
ModelManager -right-> "1" UserPrefs
UserPrefs .up.|> ReadOnlyUserPrefs
-AddressBook *--> "1" UniquePersonList
-UniquePersonList --> "~* all" Person
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
-Person *--> "*" Tag
+MyCrm *--> Entities
+ModelManager -[hidden]-> Entities
+ModelManager --> Entities
+note top of Entities: Every package within\nEntities is associated with\nMyCrm and ModelManager
+
+
+Job --> "1" Product
+Job --> "1" Contact
-Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
+Mail--> "1" Template
+Mail --> "1" Job
-ModelManager -->"~* filtered" Person
@enduml
diff --git a/docs/diagrams/ModelContactClassDiagram.puml b/docs/diagrams/ModelContactClassDiagram.puml
new file mode 100644
index 00000000000..a97aa1a2849
--- /dev/null
+++ b/docs/diagrams/ModelContactClassDiagram.puml
@@ -0,0 +1,33 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Contact {
+Class UniqueContactList
+Class Contact
+Class Address
+Class Email
+Class Name
+Class Phone
+Class Tag
+}
+
+
+MyCrm *-left-> "1" UniqueContactList
+UniqueContactList --> "~* all" Contact
+Contact *--> "1" Name
+Contact *--> "0..1" Phone
+Contact *--> "0..1" Email
+Contact *--> "0..1" Address
+Contact *--> "*" Tag
+
+Name -[hidden]right- Address
+
+Contact -[hidden]right- ModelManager
+ModelManager -->"~* filtered" Contact
+
+Contact "1" <-up- Job
+
+@enduml
diff --git a/docs/diagrams/ModelJobClassDiagram.puml b/docs/diagrams/ModelJobClassDiagram.puml
new file mode 100644
index 00000000000..83cb2908bd4
--- /dev/null
+++ b/docs/diagrams/ModelJobClassDiagram.puml
@@ -0,0 +1,36 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Job {
+Class UniqueJobList
+Class Job
+Class JobDate
+Class JobStatus
+Class JobDescription
+Class Hidden #FFF
+}
+
+
+MyCrm *-left-> "1" UniqueJobList
+UniqueJobList --> "~* all" Job
+Job *--> JobDate
+Job *--> JobDate
+Job *-->"1 received\n1 expected\n 0..1 completed" JobDate
+Hidden -[hidden]left- JobDate
+
+Job *--> "1" JobStatus
+Job *--> "1" JobDescription
+Job --> " 1 client" Contact
+Job --> " 1 fix" Product
+
+
+'Class ModelManager #FFFFFF
+Job -[hidden]right- ModelManager
+ModelManager -->"~* filtered" Job
+
+Job "1" <-up- Mail
+
+@enduml
diff --git a/docs/diagrams/ModelMailClassDiagram.puml b/docs/diagrams/ModelMailClassDiagram.puml
new file mode 100644
index 00000000000..f434870e34a
--- /dev/null
+++ b/docs/diagrams/ModelMailClassDiagram.puml
@@ -0,0 +1,23 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Mail {
+Class UniqueMailList
+Class Mail
+}
+
+
+MyCrm *-left-> "1" UniqueMailList
+UniqueMailList --> "~* all" Mail
+
+'Class ModelManager #FFFFFF
+Mail -[hidden]right- ModelManager
+ModelManager -->" ~* filtered" Mail
+
+Mail --> "1" Job
+Mail --> "1" Template
+
+@enduml
diff --git a/docs/diagrams/ModelProductClassDiagram.puml b/docs/diagrams/ModelProductClassDiagram.puml
new file mode 100644
index 00000000000..4cc6ee16bb4
--- /dev/null
+++ b/docs/diagrams/ModelProductClassDiagram.puml
@@ -0,0 +1,29 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Product {
+Class UniqueProductList
+Class Product
+Class ProductName
+Class Type
+Class Manufacturer
+Class Description
+}
+
+
+MyCrm *-left-> "1" UniqueProductList
+UniqueProductList --> "~* all" Product
+Product *--> "1" ProductName
+Product *--> "1" Type
+Product *--> "1" Description
+Product *--> "1" Manufacturer
+
+Product -[hidden]right- ModelManager
+ModelManager -->"~* filtered" Product
+
+Product "1" <-up- Job
+
+@enduml
diff --git a/docs/diagrams/ModelTemplateClassDiagram.puml b/docs/diagrams/ModelTemplateClassDiagram.puml
new file mode 100644
index 00000000000..87450115136
--- /dev/null
+++ b/docs/diagrams/ModelTemplateClassDiagram.puml
@@ -0,0 +1,27 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Template {
+Class UniqueTemplateList
+Class Template
+Class Subject
+Class Body
+}
+
+
+MyCrm *-left-> "1" UniqueTemplateList
+UniqueTemplateList --> "~* all" Template
+Template *--> "1" Subject
+Template *--> "1" Body
+
+
+'Class ModelManager #FFFFFF
+Template -[hidden]right- ModelManager
+ModelManager -->"~* filtered" Template
+
+Template "1" <-up- Mail
+
+@enduml
diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml
index 6ba585cba01..003485b88b3 100644
--- a/docs/diagrams/ParserClasses.puml
+++ b/docs/diagrams/ParserClasses.puml
@@ -9,7 +9,7 @@ Class XYZCommand
package "Parser classes"{
Interface Parser <>
-Class AddressBookParser
+Class MyCrmParser
Class XYZCommandParser
Class CliSyntax
Class ParserUtil
@@ -19,12 +19,12 @@ Class Prefix
}
Class HiddenOutside #FFFFFF
-HiddenOutside ..> AddressBookParser
+HiddenOutside ..> MyCrmParser
-AddressBookParser .down.> XYZCommandParser: creates >
+MyCrmParser .down.> XYZCommandParser: creates >
XYZCommandParser ..> XYZCommand : creates >
-AddressBookParser ..> Command : returns >
+MyCrmParser ..> Command : returns >
XYZCommandParser .up.|> Parser
XYZCommandParser ..> ArgumentMultimap
XYZCommandParser ..> ArgumentTokenizer
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index 85ac3ea2dee..95640df2c02 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -14,12 +14,15 @@ Class JsonUserPrefsStorage
Interface Storage <>
Class StorageManager
-package "AddressBook Storage" #F4F6F6{
-Interface AddressBookStorage <>
-Class JsonAddressBookStorage
-Class JsonSerializableAddressBook
-Class JsonAdaptedPerson
+package "MyCrm Storage" #F4F6F6{
+Interface MyCrmStorage <>
+Class JsonMyCrmStorage
+Class JsonSerializableMyCrm
+Class JsonAdaptedContact
Class JsonAdaptedTag
+Class JsonAdaptedJob
+Class JsonAdaptedProduct
+Class JsonAdaptedTemplate
}
}
@@ -29,15 +32,17 @@ HiddenOutside ..> Storage
StorageManager .up.|> Storage
StorageManager -up-> "1" UserPrefsStorage
-StorageManager -up-> "1" AddressBookStorage
+StorageManager -up-> "1" MyCrmStorage
Storage -left-|> UserPrefsStorage
-Storage -right-|> AddressBookStorage
+Storage -right-|> MyCrmStorage
JsonUserPrefsStorage .up.|> UserPrefsStorage
-JsonAddressBookStorage .up.|> AddressBookStorage
-JsonAddressBookStorage ..> JsonSerializableAddressBook
-JsonSerializableAddressBook --> "*" JsonAdaptedPerson
-JsonAdaptedPerson --> "*" JsonAdaptedTag
-
+JsonMyCrmStorage .up.|> MyCrmStorage
+JsonMyCrmStorage ..> JsonSerializableMyCrm
+JsonSerializableMyCrm --> "*" JsonAdaptedContact
+JsonAdaptedContact --> "*" JsonAdaptedTag
+JsonSerializableMyCrm --> "*" JsonAdaptedJob
+JsonSerializableMyCrm --> "*" JsonAdaptedProduct
+JsonSerializableMyCrm --> "*" JsonAdaptedTemplate
@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index ecae4876432..3cd926bda26 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -9,18 +9,59 @@ Interface Ui <>
Class "{abstract}\nUiPart" as UiPart
Class UiManager
Class MainWindow
-Class HelpWindow
+
+Class ThemeManager
+
+Class CommandBox
Class ResultDisplay
-Class PersonListPanel
-Class PersonCard
+
+package "MainDisplay Section" #F4F6F6{
+Class MainDisplay
+Class JobListPanel
+Class JobCard
+Class MailListPanel
+Class MailCard
+}
+
+package "SideDisplay Section" #F4F6F6{
+Class SideDisplay
+Class ContactListPanel
+Class ContactCard
+
+Class ProductListPanel
+Class ProductCard
+
+Class TemplateListPanel
+Class TemplateCard
+
+Class HistoryListPanel
+Class HistoryCard
+}
+
+package "ReportWindow Section" #F4F6F6{
+Class ReportWindow
+Class GraphDisplay
+Class JobDisplay
+}
+
Class StatusBarFooter
-Class CommandBox
+Class HelpWindow
+
+
+note "All classes in these sections\ninherit from **UiPart** class." as N1
+N1 .. "SideDisplay Section"
+N1 .. "MainDisplay Section"
+N1 .. "ReportWindow Section"
}
+
+
package Model <> {
Class HiddenModel #FFFFFF
}
+Model -[hidden]up- "MainDisplay"
+
package Logic <> {
Class HiddenLogic #FFFFFF
}
@@ -30,31 +71,55 @@ HiddenOutside ..> Ui
UiManager .left.|> Ui
UiManager -down-> "1" MainWindow
+MainWindow -left-> "1" ThemeManager
MainWindow *-down-> "1" CommandBox
MainWindow *-down-> "1" ResultDisplay
-MainWindow *-down-> "1" PersonListPanel
+MainWindow *-down-> "1" MainDisplay
+MainWindow *-down-> "1" SideDisplay
MainWindow *-down-> "1" StatusBarFooter
MainWindow --> "0..1" HelpWindow
-
-PersonListPanel -down-> "*" PersonCard
+MainWindow --> "0..1" ReportWindow
+HelpWindow -[hidden]- CommandBox
+StatusBarFooter -[hidden]- ResultDisplay
MainWindow -left-|> UiPart
-ResultDisplay --|> UiPart
+UiManager -right-> Logic
+MainWindow -right-> Logic
+
+' Main display package
+MainDisplay *-down-> "1" JobListPanel
+MainDisplay *-down-> "1" MailListPanel
+JobListPanel -down-> "*" JobCard
+MailListPanel -down-> "*" MailCard
+JobCard .down.> Model
+MailCard .down.> Model
+
+' Side display package
+SideDisplay *-down-> "1" ContactListPanel
+SideDisplay *-down-> "1" ProductListPanel
+SideDisplay *-down-> "1" TemplateListPanel
+SideDisplay *-down-> "1" HistoryListPanel
+ContactListPanel -down-> "*" ContactCard
+ProductListPanel -down-> "*" ProductCard
+TemplateListPanel -down-> "*" TemplateCard
+HistoryListPanel -down-> "*" HistoryCard
+ContactCard .down.> Model
+ProductCard .down.> Model
+TemplateCard .down.> Model
+HistoryCard .down.> Model
+
+' Report window package
+ReportWindow *-down-> "1" GraphDisplay
+GraphDisplay -[hidden]down- JobDisplay
+ReportWindow *-down-> "1" JobDisplay
+JobDisplay .down.> Model
+
+' Inherit UiPart
CommandBox --|> UiPart
-PersonListPanel --|> UiPart
-PersonCard --|> UiPart
+ResultDisplay --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow --|> UiPart
-PersonCard ..> Model
-UiManager -right-> Logic
-MainWindow -left-> Logic
-
-PersonListPanel -[hidden]left- HelpWindow
-HelpWindow -[hidden]left- CommandBox
-CommandBox -[hidden]left- ResultDisplay
-ResultDisplay -[hidden]left- StatusBarFooter
-
MainWindow -[hidden]-|> UiPart
@enduml
diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml
index bccc230a5d1..b6b64bd0831 100644
--- a/docs/diagrams/UndoRedoState2.puml
+++ b/docs/diagrams/UndoRedoState2.puml
@@ -6,9 +6,9 @@ skinparam ClassBorderColor #000000
title After command "add n/David"
package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
+ class State1 as "__mc0:MyCrm__"
+ class State2 as "__mc1:MyCrm__"
+ class State3 as "__mc2:MyCrm__"
}
State1 -[hidden]right-> State2
diff --git a/docs/diagrams/contact/AddContactActivityDiagram.puml b/docs/diagrams/contact/AddContactActivityDiagram.puml
new file mode 100644
index 00000000000..36231d881ff
--- /dev/null
+++ b/docs/diagrams/contact/AddContactActivityDiagram.puml
@@ -0,0 +1,31 @@
+@startuml
+start
+:User executes add contact command;
+:MyCRM parses user inputs;
+if() then ([InValid Input])
+ :Throws ParseException;
+ :Displays unsuccessful adding contact message on UI;
+else ([Valid Input])
+ fork
+ :Creates new **Name**;
+ fork again
+ :(Optional)
+ Creates new **Phone**;
+ fork again
+ : (Optional)
+ Creates new **Email**;
+ fork again
+ : (Optional)
+ Creates new **Address**;
+ end fork
+ :Creates new contact;
+ if () then ([Duplicate contact])
+ :Throws CommandException;
+ :Displays unsuccessful adding contact message on UI;
+ else ([Else])
+ :Stores contact into MyCRM;
+ :Displays successful adding contact message on UI;
+ endif
+endif;
+stop
+@enduml
diff --git a/docs/diagrams/contact/AddContactParseSequenceDiagram.puml b/docs/diagrams/contact/AddContactParseSequenceDiagram.puml
new file mode 100644
index 00000000000..a0e266b29ab
--- /dev/null
+++ b/docs/diagrams/contact/AddContactParseSequenceDiagram.puml
@@ -0,0 +1,82 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":AddContactCommandParser" as AddContactCommandParser LOGIC_COLOR
+participant "contactToAdd:AddContactCommand" as AddContactCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "n:Name" as Name MODEL_COLOR
+participant "c:Phone" as Phone MODEL_COLOR
+participant "e:Email" as Email MODEL_COLOR
+participant "a:Address" as Address MODEL_COLOR
+participant "contact:Contact" as Contact MODEL_COLOR
+end box
+[-> LogicManager : execute("addContact n/Sans c/83921823 e/Sans@gmail.com a/Maxwell...")
+activate LogicManager
+
+group sd parse add contact
+LogicManager -> MyCrmParser : parseCommand("addContact n/Sans c/83921823 e/Sans@gmail.com a/Maxwell...")
+activate MyCrmParser
+
+create AddContactCommandParser
+MyCrmParser -> AddContactCommandParser
+activate AddContactCommandParser
+
+AddContactCommandParser --> MyCrmParser
+deactivate AddContactCommandParser
+
+MyCrmParser -> AddContactCommandParser : parse("addContact n/Sans c/83921823 e/Sans@gmail.com a/Maxwell...")
+activate AddContactCommandParser
+
+create Name
+AddContactCommandParser -> Name : "Sans"
+activate Name
+Name --> AddContactCommandParser : n
+deactivate Name
+
+create Phone
+AddContactCommandParser -> Phone : "83921823"
+activate Phone
+Phone --> AddContactCommandParser : c
+deactivate Phone
+
+create Email
+AddContactCommandParser -> Email : "Sans@gmail.com"
+activate Email
+Email --> AddContactCommandParser : e
+deactivate Email
+
+create Address
+AddContactCommandParser -> Address : "Maxwell..."
+activate Address
+Address --> AddContactCommandParser : a
+deactivate Address
+
+create Contact
+AddContactCommandParser -> Contact : n, c, e, a
+activate Contact
+Contact --> AddContactCommandParser : contact
+deactivate Contact
+
+create AddContactCommand
+AddContactCommandParser -> AddContactCommand : contact
+activate AddContactCommand
+AddContactCommand --> AddContactCommandParser : contactToAdd
+deactivate AddContactCommand
+
+AddContactCommandParser --> MyCrmParser : contactToAdd
+deactivate AddContactCommandParser
+'Hidden arrow to position destroy marker below the end of the activation bar.
+AddContactCommandParser -[hidden]-> MyCrmParser
+destroy AddContactCommandParser
+
+MyCrmParser --> LogicManager : contactToAdd
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/AddContactSequenceDiagram.puml b/docs/diagrams/contact/AddContactSequenceDiagram.puml
new file mode 100644
index 00000000000..c4148e2add2
--- /dev/null
+++ b/docs/diagrams/contact/AddContactSequenceDiagram.puml
@@ -0,0 +1,57 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "contactToAdd:AddContactCommand" as AddContactCommand LOGIC_COLOR
+participant "c:CommandResult" as CommandResult LOGIC_COLOR
+participant ":StateManager" as StateManager LOGIC_COLOR
+participant "modifiedResult:CommandResult" as CommandResult2 LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+[-> LogicManager : execute("addContact n/Sans c/83921823 e/Sans@gmail.com a/MaxWell...")
+activate LogicManager
+
+ref over LogicManager, AddContactCommand : parse add contact
+
+LogicManager -> AddContactCommand : execute()
+activate AddContactCommand
+
+AddContactCommand -> Model : addContact(contactToAdd)
+activate Model
+
+Model --> AddContactCommand
+deactivate Model
+
+create CommandResult
+AddContactCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddContactCommand : c
+deactivate CommandResult
+
+AddContactCommand -> StateManager : handleContact(contactToAdd, c)
+activate StateManager
+
+create CommandResult2
+StateManager -> CommandResult2
+activate CommandResult2
+
+CommandResult2 --> StateManager : modifiedResult
+deactivate CommandResult2
+
+StateManager --> AddContactCommand : modifiedResult
+deactivate StateManager
+
+AddContactCommand --> LogicManager : modifiedResult
+deactivate AddContactCommand
+
+AddContactCommand -[hidden]-> LogicManager
+destroy AddContactCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/DeleteContactActivityDiagram.puml b/docs/diagrams/contact/DeleteContactActivityDiagram.puml
new file mode 100644
index 00000000000..df87eafa672
--- /dev/null
+++ b/docs/diagrams/contact/DeleteContactActivityDiagram.puml
@@ -0,0 +1,24 @@
+@startuml
+start
+:User executes delete contact command;
+:MyCRM parses user inputs;
+if () then ([InValid Input])
+ :Throws ParseException;
+ :Displays unsuccessful deleting contact message on UI;
+else ([Valid Input])
+if () then ([Invalid Index])
+ :Throws CommandException;
+ :Displays unsuccessful deleting contact message on UI;
+else ([Else])
+ :Gets contact to be deleted with index provided;
+ if () then ([Contact not linked to any jobs])
+ :Delete the specific contact from MyCRM;
+ :Displays successful deleting contact message on UI;
+ else ([Contact linked to a job]))
+ :Throws CommandException;
+ :Displays unsuccessful deleting contact message on UI;
+endif
+endif
+endif
+stop
+@enduml
diff --git a/docs/diagrams/contact/DeleteContactParseSequenceDiagram.puml b/docs/diagrams/contact/DeleteContactParseSequenceDiagram.puml
new file mode 100644
index 00000000000..cb7b8f8672c
--- /dev/null
+++ b/docs/diagrams/contact/DeleteContactParseSequenceDiagram.puml
@@ -0,0 +1,47 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":DeleteContactCommandParser" as DeleteContactCommandParser LOGIC_COLOR
+participant "contactToDelete:DeleteContactCommand" as DeleteContactCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("deleteContact 1")
+activate LogicManager
+
+group sd parse delete contact
+LogicManager -> MyCrmParser : parseCommand("deleteContact 1")
+activate MyCrmParser
+
+create DeleteContactCommandParser
+MyCrmParser -> DeleteContactCommandParser
+activate DeleteContactCommandParser
+
+DeleteContactCommandParser --> MyCrmParser
+deactivate DeleteContactCommandParser
+
+MyCrmParser -> DeleteContactCommandParser : parse("1")
+activate DeleteContactCommandParser
+
+create DeleteContactCommand
+DeleteContactCommandParser -> DeleteContactCommand : 1
+activate DeleteContactCommand
+
+DeleteContactCommand --> DeleteContactCommandParser : contactToDelete
+deactivate DeleteContactCommand
+
+DeleteContactCommandParser --> MyCrmParser : contactToDelete
+deactivate DeleteContactCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+DeleteContactCommandParser -[hidden]-> MyCrmParser
+destroy DeleteContactCommandParser
+
+MyCrmParser --> LogicManager : contactToDelete
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/DeleteContactSequenceDiagram.puml b/docs/diagrams/contact/DeleteContactSequenceDiagram.puml
new file mode 100644
index 00000000000..90627cb4c05
--- /dev/null
+++ b/docs/diagrams/contact/DeleteContactSequenceDiagram.puml
@@ -0,0 +1,59 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "contactToDelete:DeleteContactCommand" as DeleteContactCommand LOGIC_COLOR
+participant "lastShownList: List" as ListContact LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("deleteContact 1")
+activate LogicManager
+
+ref over LogicManager, DeleteContactCommand : parse delete contact
+
+LogicManager -> DeleteContactCommand : execute()
+
+activate DeleteContactCommand
+
+DeleteContactCommand -> Model : getFilteredContactList()
+activate Model
+Model --> DeleteContactCommand : lastShownList
+deactivate Model
+
+DeleteContactCommand -> ListContact : get(index)
+activate ListContact
+ListContact --> DeleteContactCommand : contactToDelete
+deactivate ListContact
+
+DeleteContactCommand -> Model : deleteContact(contactToDelete)
+activate Model
+Model --> DeleteContactCommand
+deactivate Model
+
+DeleteContactCommand -> Model : updateFilteredContactList(PREDICATE_SHOW_NOT_HIDDEN_CONTACTS)
+activate Model
+Model --> DeleteContactCommand
+deactivate Model
+
+create CommandResult
+DeleteContactCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> DeleteContactCommand
+deactivate CommandResult
+
+DeleteContactCommand --> LogicManager : result
+deactivate DeleteContactCommand
+
+DeleteContactCommand -[hidden]-> LogicManager
+destroy DeleteContactCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/EditContactActivityDiagram.puml b/docs/diagrams/contact/EditContactActivityDiagram.puml
new file mode 100644
index 00000000000..df1230fa496
--- /dev/null
+++ b/docs/diagrams/contact/EditContactActivityDiagram.puml
@@ -0,0 +1,25 @@
+@startuml
+start
+:User executes edit contact command;
+:MyCRM parses user inputs;
+if () then ([InValid Input])
+ :Throws ParseException;
+ :Displays unsuccessful editing contact message on UI;
+else ([Valid Input])
+if () then ([Invalid Index])
+ :Throws CommandException;
+ :Displays unsuccessful editing contact on UI;
+else ([Else])
+:Gets contact to be edited with index provided;
+:Creates edited contact;
+if () then ([Duplicate Contact])
+ :Throws CommandException;
+ :Displays unsuccessful editing contact on UI;
+else ([Else])
+:Replaces specific contact with edited contact;
+:Displays successful editing contact message on UI;
+endif
+endif
+endif
+stop
+@enduml
diff --git a/docs/diagrams/contact/EditContactParseSequenceDiagram.puml b/docs/diagrams/contact/EditContactParseSequenceDiagram.puml
new file mode 100644
index 00000000000..75e793f0c87
--- /dev/null
+++ b/docs/diagrams/contact/EditContactParseSequenceDiagram.puml
@@ -0,0 +1,65 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":EditContactCommandParser" as EditContactCommandParser LOGIC_COLOR
+participant "contactToEdit:EditContactCommand" as EditContactCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "editContactDescriptor:EditContactDescriptor" as EditContactDescriptor MODEL_COLOR
+end box
+[-> LogicManager : execute("editContact 1 n/Frisks c/88888888 e/Frisks@gmail.com a/Jurong West")
+activate LogicManager
+
+group sd parse edit contact
+LogicManager -> MyCrmParser : parseCommand("editContact 1 n/Frisks c/88888888 e/Frisks@gmail.com a/Jurong West")
+activate MyCrmParser
+
+create EditContactCommandParser
+MyCrmParser -> EditContactCommandParser
+activate EditContactCommandParser
+
+EditContactCommandParser --> MyCrmParser
+deactivate EditContactCommandParser
+
+MyCrmParser -> EditContactCommandParser : parse("1 n/Frisks c/88888888 e/Frisks@gmail.com a/Jurong West")
+activate EditContactCommandParser
+
+create EditContactDescriptor
+EditContactCommandParser -> EditContactDescriptor
+activate EditContactDescriptor
+EditContactDescriptor --> EditContactCommandParser : editContactDescriptor
+deactivate EditContactDescriptor
+
+EditContactCommandParser -> EditContactDescriptor : setName("Frisks")
+
+EditContactCommandParser -> EditContactDescriptor : setPhone("88888888")
+
+EditContactCommandParser -> EditContactDescriptor : setEmail("Frisks@gmail.com")
+
+EditContactCommandParser -> EditContactDescriptor : setAddress("Jurong West")
+
+
+create EditContactCommand
+EditContactCommandParser -> EditContactCommand : 1, editContactDescriptor
+activate EditContactCommand
+
+EditContactCommand --> EditContactCommandParser : contactToEdit
+deactivate EditContactCommand
+
+EditContactCommandParser --> MyCrmParser : contactToEdit
+deactivate EditContactCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+EditContactCommandParser -[hidden]-> MyCrmParser
+destroy EditContactCommandParser
+
+MyCrmParser --> LogicManager : contactToEdit
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/EditContactSequenceDiagram.puml b/docs/diagrams/contact/EditContactSequenceDiagram.puml
new file mode 100644
index 00000000000..d0d0222627c
--- /dev/null
+++ b/docs/diagrams/contact/EditContactSequenceDiagram.puml
@@ -0,0 +1,75 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "contactToEdit:EditContactCommand" as EditContactCommand LOGIC_COLOR
+participant "lastShownList: List" as ListContact LOGIC_COLOR
+participant "**<>**\nContact" as Contact LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("editContact 1 n/Frisks c/88888888 e/Frisks@gmail.com a/Jurong West")
+activate LogicManager
+
+ref over LogicManager, EditContactCommand : parse edit contact
+
+LogicManager -> EditContactCommand : execute()
+
+activate EditContactCommand
+
+EditContactCommand -> Model : getFilteredContactList()
+activate Model
+Model --> EditContactCommand : lastShownList
+deactivate Model
+
+EditContactCommand -> ListContact : get(index)
+activate ListContact
+ListContact --> EditContactCommand : contactToEdit
+deactivate ListContact
+
+EditContactCommand -> Contact : createEditedContact(contactToEdit, editContactDescriptor)
+activate Contact
+Contact --> EditContactCommand : editedContact
+deactivate Contact
+
+EditContactCommand -> Model : setContact(contactToEdit, editedContact)
+activate Model
+Model --> EditContactCommand
+deactivate Model
+
+EditContactCommand -> Model : updateFilteredContactList(PREDICATE_SHOW_NOT_HIDDEN_CONTACTS)
+activate Model
+Model --> EditContactCommand
+deactivate Model
+
+EditContactCommand -> Model : getLatestJobPredicate()
+activate Model
+Model --> EditContactCommand : latestJobPredicate
+deactivate Model
+
+EditContactCommand -> Model : updateFilteredJobList(latestJobPredicate)
+activate Model
+Model --> EditContactCommand
+deactivate Model
+
+create CommandResult
+EditContactCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> EditContactCommand
+deactivate CommandResult
+
+EditContactCommand --> LogicManager : result
+deactivate EditContactCommand
+
+EditContactCommand -[hidden]-> LogicManager
+destroy EditContactCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/FindContactActivityDiagram.puml b/docs/diagrams/contact/FindContactActivityDiagram.puml
new file mode 100644
index 00000000000..e1d0efdaecf
--- /dev/null
+++ b/docs/diagrams/contact/FindContactActivityDiagram.puml
@@ -0,0 +1,15 @@
+@startuml
+start
+:User executes find contact command;
+:MyCRM parses user inputs;
+if () then ([InValid Input])
+ :Throws ParseException;
+ :Displays unsuccessful finding contact message on UI;
+else ([Valid Input])
+:Gets contact names predicate with keywords provided;
+:Update contact list with new predicate:
+showing contacts whose names contain keywords;
+:Displays successful finding contact message on UI;
+endif
+stop
+@enduml
diff --git a/docs/diagrams/contact/FindContactParseSequenceDiagram.puml b/docs/diagrams/contact/FindContactParseSequenceDiagram.puml
new file mode 100644
index 00000000000..50bd066285a
--- /dev/null
+++ b/docs/diagrams/contact/FindContactParseSequenceDiagram.puml
@@ -0,0 +1,55 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":FindContactCommandParser" as FindContactCommandParser LOGIC_COLOR
+participant ":FindContactCommand" as FindContactCommand LOGIC_COLOR
+participant "keywordsPredicate:NameContainsKeywordsPredicate" as NameContainsKeywordsPredicate LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("findContact Frisks")
+activate LogicManager
+
+group sd parse find contact
+LogicManager -> MyCrmParser : parseCommand("findContact Frisks")
+activate MyCrmParser
+
+create FindContactCommandParser
+MyCrmParser -> FindContactCommandParser
+activate FindContactCommandParser
+
+FindContactCommandParser --> MyCrmParser
+deactivate FindContactCommandParser
+
+MyCrmParser -> FindContactCommandParser : parse("Frisks")
+activate FindContactCommandParser
+
+create NameContainsKeywordsPredicate
+FindContactCommandParser -> NameContainsKeywordsPredicate: "Frisks"
+activate NameContainsKeywordsPredicate
+NameContainsKeywordsPredicate --> FindContactCommandParser : keywordsPredicate
+deactivate NameContainsKeywordsPredicate
+
+create FindContactCommand
+FindContactCommandParser -> FindContactCommand : keywordsPredicate
+activate FindContactCommand
+
+FindContactCommand --> FindContactCommandParser
+deactivate FindContactCommand
+
+FindContactCommandParser --> MyCrmParser
+deactivate FindContactCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+FindContactCommandParser -[hidden]-> MyCrmParser
+destroy FindContactCommandParser
+
+
+MyCrmParser --> LogicManager :
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/FindContactSequenceDiagram.puml b/docs/diagrams/contact/FindContactSequenceDiagram.puml
new file mode 100644
index 00000000000..9275baa51b8
--- /dev/null
+++ b/docs/diagrams/contact/FindContactSequenceDiagram.puml
@@ -0,0 +1,43 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":FindContactCommand" as FindContactCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("findContact Frisks")
+activate LogicManager
+
+ref over LogicManager, FindContactCommand : parse find contact
+
+LogicManager -> FindContactCommand : execute()
+
+activate FindContactCommand
+
+FindContactCommand -> Model : updateFilteredContactList(keywordsPredicate)
+activate Model
+Model --> FindContactCommand
+deactivate Model
+
+create CommandResult
+FindContactCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> FindContactCommand
+deactivate CommandResult
+
+FindContactCommand --> LogicManager : result
+deactivate FindContactCommand
+
+FindContactCommand -[hidden]-> LogicManager
+destroy FindContactCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/HideContactActivityDiagram.puml b/docs/diagrams/contact/HideContactActivityDiagram.puml
new file mode 100644
index 00000000000..e4eab2adcf3
--- /dev/null
+++ b/docs/diagrams/contact/HideContactActivityDiagram.puml
@@ -0,0 +1,24 @@
+@startuml
+start
+:User executes hide contact command;
+:MyCRM parses user inputs;
+if () then ([InValid Input])
+ :Throws ParseException;
+ :Displays unsuccessful hiding contact message on UI;
+else ([Valid Input])
+if () then ([Invalid Index])
+ :Throws CommandException;
+ :Displays unsuccessful hiding contact on UI;
+else ([Else])
+:Gets contact to be hidden with index provided;
+if () then ([Contact is not hidden])
+:Hide the specific contact from MyCRM;
+:Displays successful hiding contact message on UI;
+else ([Contact is hidden])
+:Throws CommandException;
+:Displays unsuccessful hiding contact message on UI;
+endif
+endif
+endif
+stop
+@enduml
diff --git a/docs/diagrams/contact/HideContactParserSequenceDiagram.puml b/docs/diagrams/contact/HideContactParserSequenceDiagram.puml
new file mode 100644
index 00000000000..d46cd5eb7e7
--- /dev/null
+++ b/docs/diagrams/contact/HideContactParserSequenceDiagram.puml
@@ -0,0 +1,47 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":HideContactCommandParser" as HideContactCommandParser LOGIC_COLOR
+participant "contactToHide:HideContactCommand" as HideContactCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("hideContact 1")
+activate LogicManager
+
+group sd parse hide contact
+LogicManager -> MyCrmParser : parseCommand("hideContact 1")
+activate MyCrmParser
+
+create HideContactCommandParser
+MyCrmParser -> HideContactCommandParser
+activate HideContactCommandParser
+
+HideContactCommandParser --> MyCrmParser
+deactivate HideContactCommandParser
+
+MyCrmParser -> HideContactCommandParser : parse("1")
+activate HideContactCommandParser
+
+create HideContactCommand
+HideContactCommandParser -> HideContactCommand : 1
+activate HideContactCommand
+
+HideContactCommand --> HideContactCommandParser : contactToHide
+deactivate HideContactCommand
+
+HideContactCommandParser --> MyCrmParser : contactToHide
+deactivate HideContactCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+HideContactCommandParser -[hidden]-> MyCrmParser
+destroy HideContactCommandParser
+
+MyCrmParser --> LogicManager : contactToHide
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/HideContactSequenceDiagram.puml b/docs/diagrams/contact/HideContactSequenceDiagram.puml
new file mode 100644
index 00000000000..318815d4287
--- /dev/null
+++ b/docs/diagrams/contact/HideContactSequenceDiagram.puml
@@ -0,0 +1,59 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "contactToHide:HideContactCommand" as HideContactCommand LOGIC_COLOR
+participant "lastShownList: List" as ListContact LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("hideContact 1")
+activate LogicManager
+
+ref over LogicManager, HideContactCommand : parse hide contact
+
+LogicManager -> HideContactCommand : execute()
+
+activate HideContactCommand
+
+HideContactCommand -> Model : getFilteredContactList()
+activate Model
+Model --> HideContactCommand : lastShownList
+deactivate Model
+
+HideContactCommand -> ListContact : get(index)
+activate ListContact
+ListContact --> HideContactCommand : contactToHide
+deactivate ListContact
+
+HideContactCommand -> Model : hideContact(contactToHide)
+activate Model
+Model --> HideContactCommand
+deactivate Model
+
+HideContactCommand -> Model : updateFilteredContactList(PREDICATE_SHOW_NOT_HIDDEN_CONTACTS)
+activate Model
+Model --> HideContactCommand
+deactivate Model
+
+create CommandResult
+HideContactCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> HideContactCommand
+deactivate CommandResult
+
+HideContactCommand --> LogicManager : result
+deactivate HideContactCommand
+
+HideContactCommand -[hidden]-> LogicManager
+destroy HideContactCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/ListContactActivityDiagram.puml b/docs/diagrams/contact/ListContactActivityDiagram.puml
new file mode 100644
index 00000000000..e97a1af9e03
--- /dev/null
+++ b/docs/diagrams/contact/ListContactActivityDiagram.puml
@@ -0,0 +1,18 @@
+@startuml
+start
+:User executes list contact command;
+:MyCRM parses user inputs;
+if () then ([InValid Input])
+ :Throws ParseException;
+ :Displays unsuccessful listing contact message on UI;
+else ([Valid Input])
+:Gets keyword for predicate of listContact;
+if () then ([predicate "-a"])
+:Shows all contacts in the list including hidden ones ;
+else ([no predicate])
+:Shows not hidden contacts in the list;
+endif;
+:Displays successful listing contact message on UI;
+endif
+stop
+@enduml
diff --git a/docs/diagrams/contact/ListContactParseSequenceDiagram.puml b/docs/diagrams/contact/ListContactParseSequenceDiagram.puml
new file mode 100644
index 00000000000..2e1d86400b9
--- /dev/null
+++ b/docs/diagrams/contact/ListContactParseSequenceDiagram.puml
@@ -0,0 +1,55 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":ListContactCommandParser" as ListContactCommandParser LOGIC_COLOR
+participant ":ListContactCommand" as ListContactCommand LOGIC_COLOR
+participant "listPredicate:Predicate" as PredicateContact LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("listContact -a")
+activate LogicManager
+
+group sd parse list contact
+LogicManager -> MyCrmParser : parseCommand("listContact -a")
+activate MyCrmParser
+
+create ListContactCommandParser
+MyCrmParser -> ListContactCommandParser
+activate ListContactCommandParser
+
+ListContactCommandParser --> MyCrmParser
+deactivate ListContactCommandParser
+
+MyCrmParser -> ListContactCommandParser : parse("listContact -a")
+activate ListContactCommandParser
+
+create PredicateContact
+ListContactCommandParser -> PredicateContact : "-a"
+activate PredicateContact
+PredicateContact --> ListContactCommandParser : listPredicate
+deactivate PredicateContact
+
+create ListContactCommand
+ListContactCommandParser -> ListContactCommand : listPredicate
+activate ListContactCommand
+
+ListContactCommand --> ListContactCommandParser
+deactivate ListContactCommand
+
+ListContactCommandParser --> MyCrmParser
+deactivate ListContactCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+ListContactCommandParser -[hidden]-> MyCrmParser
+destroy ListContactCommandParser
+
+
+MyCrmParser --> LogicManager :
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/ListContactSequenceDiagram.puml b/docs/diagrams/contact/ListContactSequenceDiagram.puml
new file mode 100644
index 00000000000..0c5cb1143db
--- /dev/null
+++ b/docs/diagrams/contact/ListContactSequenceDiagram.puml
@@ -0,0 +1,43 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":ListContactCommand" as ListContactCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("listContact -a")
+activate LogicManager
+
+ref over LogicManager, ListContactCommand : parse list contact
+
+LogicManager -> ListContactCommand : execute()
+
+activate ListContactCommand
+
+ListContactCommand -> Model : updateFilteredContactList(listPredicate)
+activate Model
+Model --> ListContactCommand
+deactivate Model
+
+create CommandResult
+ListContactCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> ListContactCommand
+deactivate CommandResult
+
+ListContactCommand --> LogicManager : result
+deactivate ListContactCommand
+
+ListContactCommand -[hidden]-> LogicManager
+destroy ListContactCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/UndoHideContactActivityDiagram.puml b/docs/diagrams/contact/UndoHideContactActivityDiagram.puml
new file mode 100644
index 00000000000..559dd24861a
--- /dev/null
+++ b/docs/diagrams/contact/UndoHideContactActivityDiagram.puml
@@ -0,0 +1,24 @@
+@startuml
+start
+:User executes undo hide contact command;
+:MyCRM parses user inputs;
+if () then ([InValid Input])
+ :Throws ParseException;
+ :Displays unsuccessful undoing hiding contact message on UI;
+else ([Valid Input])
+if () then ([Invalid Index])
+ :Throws CommandException;
+ :Displays unsuccessful undoing hiding contact on UI;
+else ([Else])
+:Gets contact to undo hiding with index provided;
+if () then ([Contact is hidden])
+:Undo Hiding the specific contact from MyCRM;
+:Displays successful undoing hiding contact message on UI;
+else ([Contact is not hidden])
+:Throws CommandException;
+:Displays unsuccessful undoing hiding contact message on UI;
+endif
+endif
+endif
+stop
+@enduml
diff --git a/docs/diagrams/contact/UndoHideContactParseSequenceDiagram.puml b/docs/diagrams/contact/UndoHideContactParseSequenceDiagram.puml
new file mode 100644
index 00000000000..a45ff2e298b
--- /dev/null
+++ b/docs/diagrams/contact/UndoHideContactParseSequenceDiagram.puml
@@ -0,0 +1,47 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":UndoHideContactCommandParser" as UndoHideContactCommandParser LOGIC_COLOR
+participant "contactToUndoHide:UndoHideContactCommand" as UndoHideContactCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("undoHideContact 1")
+activate LogicManager
+
+group sd parse undo hide contact
+LogicManager -> MyCrmParser : parseCommand("undoHideContact 1")
+activate MyCrmParser
+
+create UndoHideContactCommandParser
+MyCrmParser -> UndoHideContactCommandParser
+activate UndoHideContactCommandParser
+
+UndoHideContactCommandParser --> MyCrmParser
+deactivate UndoHideContactCommandParser
+
+MyCrmParser -> UndoHideContactCommandParser : parse("1")
+activate UndoHideContactCommandParser
+
+create UndoHideContactCommand
+UndoHideContactCommandParser -> UndoHideContactCommand : 1
+activate UndoHideContactCommand
+
+UndoHideContactCommand --> UndoHideContactCommandParser : contactToUndoHide
+deactivate UndoHideContactCommand
+
+UndoHideContactCommandParser --> MyCrmParser : contactToUndoHide
+deactivate UndoHideContactCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+UndoHideContactCommandParser -[hidden]-> MyCrmParser
+destroy UndoHideContactCommandParser
+
+MyCrmParser --> LogicManager : contactToUndoHide
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/contact/UndoHideContactSequenceDiagram.puml b/docs/diagrams/contact/UndoHideContactSequenceDiagram.puml
new file mode 100644
index 00000000000..6bcdb7f7440
--- /dev/null
+++ b/docs/diagrams/contact/UndoHideContactSequenceDiagram.puml
@@ -0,0 +1,59 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "contactToUndoHide:UndoHideContactCommand" as UndoHideContactCommand LOGIC_COLOR
+participant "lastShownList: List" as ListContact LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("undoHideContact 1")
+activate LogicManager
+
+ref over LogicManager, UndoHideContactCommand : parse undo hide contact
+
+LogicManager -> UndoHideContactCommand : execute()
+
+activate UndoHideContactCommand
+
+UndoHideContactCommand -> Model : getFilteredContactList()
+activate Model
+Model --> UndoHideContactCommand : lastShownList
+deactivate Model
+
+UndoHideContactCommand -> ListContact : get(index)
+activate ListContact
+ListContact --> UndoHideContactCommand : contactToUndoHide
+deactivate ListContact
+
+UndoHideContactCommand -> Model : UndoHideContact(contactToUndoHide)
+activate Model
+Model --> UndoHideContactCommand
+deactivate Model
+
+UndoHideContactCommand -> Model : updateFilteredContactList(PREDICATE_SHOW_NOT_HIDDEN_CONTACTS)
+activate Model
+Model --> UndoHideContactCommand
+deactivate Model
+
+create CommandResult
+UndoHideContactCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> UndoHideContactCommand
+deactivate CommandResult
+
+UndoHideContactCommand --> LogicManager : result
+deactivate UndoHideContactCommand
+
+UndoHideContactCommand -[hidden]-> LogicManager
+destroy UndoHideContactCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/job/AddJobActivityDiagram.puml b/docs/diagrams/job/AddJobActivityDiagram.puml
new file mode 100644
index 00000000000..9968d010f02
--- /dev/null
+++ b/docs/diagrams/job/AddJobActivityDiagram.puml
@@ -0,0 +1,49 @@
+@startuml
+sprite $rake [16x16/8] {
+0000000000000000
+0000000jj0000000
+0000000jj0000000
+0005555jj5555000
+000jjeejjeejj000
+000jj00jj00jj000
+000jj00jj00jj000
+0000000000000000
+}
+
+start
+:User executes add job command;
+:MyCRM parses user inputs;
+if () then ([Valid inputs])
+ if () then ([Contact Index was provided])
+ else ([else])
+ :Assign contact to job <$rake>;
+ if () then([operation aborted])
+ stop;
+ else ([else])
+ endif
+ endif
+
+if () then ([Product Index was provided])
+ else ([else])
+ :Assign product to job <$rake>;
+ if () then([operation aborted])
+ stop;
+ else ([else])
+ endif
+ endif
+
+ :Creates new job;
+ if () then ([Duplicate contact])
+ :Throws CommandException;
+ :Displays unsuccessful adding of job on UI;
+ else ([Else])
+ :Stores job into MyCRM;
+ :Displays successful add job on UI;
+ endif
+
+else ([Invalid inputs])
+ :Throws ParseException;
+ :Displays unsuccessful add job on UI;
+endif
+stop
+@enduml
diff --git a/docs/diagrams/job/AddJobParseSequenceDiagram.puml b/docs/diagrams/job/AddJobParseSequenceDiagram.puml
new file mode 100644
index 00000000000..ce05f7e9c64
--- /dev/null
+++ b/docs/diagrams/job/AddJobParseSequenceDiagram.puml
@@ -0,0 +1,82 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":AddJobCommandParser" as AddJobCommandParser LOGIC_COLOR
+participant "ToAdd:AddJobCommand" as AddJobCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "desc:JobDescription" as JobDescription MODEL_COLOR
+participant "fee:JobFee" as JobFee MODEL_COLOR
+participant "recv:JobDate" as RECEIVED_DATE MODEL_COLOR
+participant "by:JobDate" as EXPECTED_COMPLETION MODEL_COLOR
+participant "job:Job" as Job MODEL_COLOR
+end box
+[-> LogicManager :execute("addJob d/Fix CPU fee/30.00 recv/29/10/2021 by/30/10/2021 c/1")
+activate LogicManager
+
+group sd parse add contact
+LogicManager -> MyCrmParser : parseCommand("addJob d/Fix CPU fee/30.00 recv/29/10/2021 by/30/10/2021 c/1")
+activate MyCrmParser
+
+create AddJobCommandParser
+MyCrmParser -> AddJobCommandParser
+activate AddJobCommandParser
+
+AddJobCommandParser --> MyCrmParser
+deactivate AddJobCommandParser
+
+MyCrmParser -> AddJobCommandParser : parse("addJob d/Fix CPU fee/30.00 recv/29/10/2021 by/30/10/2021 c/1")
+activate AddJobCommandParser
+
+create JobDescription
+AddJobCommandParser -> JobDescription : "Fix CPU"
+activate JobDescription
+JobDescription --> AddJobCommandParser : desc
+deactivate JobDescription
+
+create JobFee
+AddJobCommandParser -> JobFee : "30.00"
+activate JobFee
+JobFee --> AddJobCommandParser : fee
+deactivate JobFee
+
+create RECEIVED_DATE
+AddJobCommandParser -> RECEIVED_DATE : "29/10/2021"
+activate RECEIVED_DATE
+RECEIVED_DATE --> AddJobCommandParser : recv
+deactivate RECEIVED_DATE
+
+create EXPECTED_COMPLETION
+AddJobCommandParser -> EXPECTED_COMPLETION : "30/10/2021"
+activate EXPECTED_COMPLETION
+EXPECTED_COMPLETION --> AddJobCommandParser : by
+deactivate EXPECTED_COMPLETION
+
+create Job
+AddJobCommandParser -> Job : desc, fee, recv, by
+activate Job
+Job --> AddJobCommandParser : job
+deactivate Job
+
+create AddJobCommand
+AddJobCommandParser -> AddJobCommand : job, contactIndex
+activate AddJobCommand
+AddJobCommand --> AddJobCommandParser : ToAdd
+deactivate AddJobCommand
+
+AddJobCommandParser --> MyCrmParser : ToAdd
+deactivate AddJobCommandParser
+'Hidden arrow to position destroy marker below the end of the activation bar.
+AddJobCommandParser -[hidden]-> MyCrmParser
+destroy AddJobCommandParser
+
+MyCrmParser --> LogicManager : jobToAdd
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/job/AddJobSequenceDiagram.puml b/docs/diagrams/job/AddJobSequenceDiagram.puml
new file mode 100644
index 00000000000..c8df510f044
--- /dev/null
+++ b/docs/diagrams/job/AddJobSequenceDiagram.puml
@@ -0,0 +1,86 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":AddJobCommandParser" as AddJobCommandParser LOGIC_COLOR
+participant "a:AddJobCommand" as AddJobCommand LOGIC_COLOR
+participant ":StateManager" as StateManager LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("addJob c/1 p/1 d/Fix...")
+activate LogicManager
+
+LogicManager -> MyCrmParser : parseCommandWord("addJob c/1 p/1 d/Fix...")
+activate MyCrmParser
+MyCrmParser --> LogicManager
+deactivate MyCrmParser
+
+LogicManager -> StateManager : isCommandAllowed("addJob")
+activate StateManager
+StateManager --> LogicManager
+deactivate StateManager
+
+LogicManager -> MyCrmParser : parseCommand("addJob c/1 p/1 d/Fix...")
+activate MyCrmParser
+
+create AddJobCommandParser
+MyCrmParser -> AddJobCommandParser
+activate AddJobCommandParser
+
+AddJobCommandParser --> MyCrmParser
+deactivate AddJobCommandParser
+
+MyCrmParser -> AddJobCommandParser : parse("c/1 p/1 d/Fix...")
+activate AddJobCommandParser
+
+create AddJobCommand
+AddJobCommandParser -> AddJobCommand
+activate AddJobCommand
+
+AddJobCommand --> AddJobCommandParser : a
+deactivate AddJobCommand
+
+AddJobCommandParser --> MyCrmParser : a
+deactivate AddJobCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+AddJobCommandParser -[hidden]-> MyCrmParser
+destroy AddJobCommandParser
+
+MyCrmParser --> LogicManager : a
+deactivate MyCrmParser
+
+LogicManager -> AddJobCommand : execute()
+activate AddJobCommand
+
+AddJobCommand -> StateManager : handleAddJob(jobToAdd)
+activate StateManager
+
+StateManager -> Model : addJob(jobToAdd)
+activate Model
+
+Model --> StateManager
+deactivate Model
+
+create CommandResult
+StateManager -> CommandResult
+activate CommandResult
+
+CommandResult --> StateManager
+deactivate CommandResult
+
+StateManager --> AddJobCommand
+deactivate StateManager
+
+AddJobCommand --> LogicManager : result
+deactivate AddJobCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/job/AddJobSequenceDiagramShort.puml b/docs/diagrams/job/AddJobSequenceDiagramShort.puml
new file mode 100644
index 00000000000..95f6222aba8
--- /dev/null
+++ b/docs/diagrams/job/AddJobSequenceDiagramShort.puml
@@ -0,0 +1,54 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "toAdd:AddJobCommand" as AddJobCommand LOGIC_COLOR
+participant "lastShownList: List" as ListContact LOGIC_COLOR
+participant ":StateManager" as StateManager LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+[-> LogicManager : execute("addJob d/Fix CPU fee/30.00 by/30/10/2021 c/1")
+activate LogicManager
+
+ref over LogicManager, AddJobCommand : parse add job
+
+LogicManager -> AddJobCommand : execute()
+activate AddJobCommand
+
+AddJobCommand -> Model : getFilteredContactList()
+activate Model
+Model --> AddJobCommand : lastShownList
+deactivate Model
+
+AddJobCommand -> ListContact : get(contactIndex)
+activate ListContact
+ListContact --> AddJobCommand : contact
+deactivate ListContact
+
+AddJobCommand -> StateManager : handleAddJob(jobToAdd)
+activate StateManager
+
+create CommandResult
+StateManager -> CommandResult
+activate CommandResult
+
+CommandResult --> StateManager
+deactivate CommandResult
+
+StateManager --> AddJobCommand
+deactivate StateManager
+
+AddJobCommand --> LogicManager : result
+deactivate AddJobCommand
+
+AddJobCommand -[hidden]-> LogicManager
+destroy AddJobCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/job/AssigningContactActivityDiagram.puml b/docs/diagrams/job/AssigningContactActivityDiagram.puml
new file mode 100644
index 00000000000..d282cf91786
--- /dev/null
+++ b/docs/diagrams/job/AssigningContactActivityDiagram.puml
@@ -0,0 +1,32 @@
+@startuml
+title :Assign contact to job
+
+start
+repeat :MyCRM asks user to provide contact for the job;
+:User issues command;
+:MyCRM parses user input;
+if () then([invalid input])
+ :Throws Parse Exception;
+ :Displays unsuccessful command execution in UI;
+else ([valid input])
+ :MyCRM executes user command;
+ if () then([abort command])
+ :Operation will be aborted;
+ :Displays abortion of current operation in UI;
+ stop;
+ else ([else])
+ if () then( [existing contact selected])
+ :Existing contact will be linked to job;
+ else ([else])
+ if () then([new contact created])
+ :Newly created contact will be linked to job;
+ else ([else])
+ endif
+ endif
+ endif
+endif
+repeat while () is ([contact not assigned to job])
+->[else];
+:Displays successful linking of contact to job in UI;
+stop
+@enduml
diff --git a/docs/diagrams/job/AssigningProductActivtyDiagram.puml b/docs/diagrams/job/AssigningProductActivtyDiagram.puml
new file mode 100644
index 00000000000..0d4e7f52e8b
--- /dev/null
+++ b/docs/diagrams/job/AssigningProductActivtyDiagram.puml
@@ -0,0 +1,32 @@
+@startuml
+title :Assign product to job
+
+start
+repeat :MyCRM asks user to provide product for the job;
+:User issues command;
+:MyCRM parses user input;
+if () then([invalid input])
+ :Throws Parse Exception;
+ :Displays unsuccessful command execution in UI;
+else ([valid input])
+ :MyCRM executes user command;
+ if () then([abort command])
+ :Operation will be aborted;
+ :Displays abortion of current operation in UI;
+ stop;
+ else ([else])
+ if () then( [existing product selected])
+ :Existing product will be linked to job;
+ else ([else])
+ if () then([new product created])
+ :Newly created product will be linked to job;
+ else ([else])
+ endif
+ endif
+ endif
+endif
+repeat while () is ([product not assigned to job])
+->[else];
+:Displays successful linking of product to job in UI;
+stop
+@enduml
diff --git a/docs/diagrams/job/EditJobActivityDiagram.puml b/docs/diagrams/job/EditJobActivityDiagram.puml
new file mode 100644
index 00000000000..7475aa70432
--- /dev/null
+++ b/docs/diagrams/job/EditJobActivityDiagram.puml
@@ -0,0 +1,60 @@
+@startuml
+sprite $rake [16x16/8] {
+0000000000000000
+0000000jj0000000
+0000000jj0000000
+0005555jj5555000
+000jjeejjeejj000
+000jj00jj00jj000
+000jj00jj00jj000
+0000000000000000
+}
+
+start
+:User executes edit job command;
+:MyCRM parses user inputs;
+if () then ([Valid inputs])
+ if () then ([Invalid index])
+ :Throws ParseException;
+ :Displays unsuccessful edit job on UI;
+ else ([else])
+ :Gets job to be edited with index provided;
+ if () then ([Contact should be reassigned)])
+ if () then ([else])
+ else ([Contact index not provided])
+ :MyCRM asks user to assign contact <$rake>;
+ if () then([operation aborted])
+ stop;
+ else ([else])
+ endif
+ endif
+ else ([else])
+ endif
+ if () then ([Product should be reassigned)])
+ if () then ([else])
+ else ([Product index not provided])
+ :MyCRM asks user to assign product <$rake>;
+ if () then([operation aborted])
+ stop;
+ else ([else])
+ endif
+ endif
+ else ([else])
+ endif
+
+ :Creates edited job;
+ if () then ([Duplicate job])
+ :Throws CommandException;
+ :Displays unsuccessful adding of job on UI;
+ else ([Else])
+ :Stores job into MyCRM;
+ :Replaces job being edited with edited job;
+ :Displays successful editing contact message on UI;
+ endif
+ endif
+else ([Invalid inputs])
+ :Throws ParseException;
+ :Displays unsuccessful edit job on UI;
+endif
+stop
+@enduml
diff --git a/docs/diagrams/job/EditJobSequenceDiagram.puml b/docs/diagrams/job/EditJobSequenceDiagram.puml
new file mode 100644
index 00000000000..5faa4a3cc2e
--- /dev/null
+++ b/docs/diagrams/job/EditJobSequenceDiagram.puml
@@ -0,0 +1,79 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "jobToEdit:EditJobCommand" as EditJobCommand LOGIC_COLOR
+participant "lastShownJobList: List" as ListJob LOGIC_COLOR
+participant "**<>**\nJob" as Job LOGIC_COLOR
+participant ":StateManager" as StateManager LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("editJob 1 c/2 p/3")
+activate LogicManager
+
+ref over LogicManager, EditJobCommand : parse edit job
+
+LogicManager -> EditJobCommand : execute()
+
+activate EditJobCommand
+
+EditJobCommand -> Model : getFilteredJobList()
+activate Model
+Model --> EditJobCommand : lastShownJobList
+deactivate Model
+
+EditJobCommand -> Model : getFilteredContactList()
+activate Model
+Model --> EditJobCommand : lastShownContactList
+deactivate Model
+
+EditJobCommand -> Model : getFilteredProductList()
+activate Model
+Model --> EditJobCommand : lastShownProductList
+deactivate Model
+
+EditJobCommand -> ListJob : get(jobIndex)
+activate ListJob
+ListJob --> EditJobCommand : jobToEdit
+deactivate ListJob
+
+EditJobCommand -> Job : createEditedJob(jobToEdit, editJobDescriptor, "lastShownContactList, lastShownProductList)
+activate Job
+Job --> EditJobCommand : editedJob
+deactivate Job
+
+EditJobCommand -> StateManager : handleEditJob(jobToEdit, editedJob, shouldEditContact, shouldEditProduct)
+activate StateManager
+
+StateManager -> Model : setJob(jobToEdit, editedJob)
+activate Model
+
+Model --> StateManager
+deactivate Model
+
+create CommandResult
+StateManager -> CommandResult
+activate CommandResult
+
+CommandResult --> StateManager
+deactivate CommandResult
+
+StateManager --> EditJobCommand
+deactivate StateManager
+
+EditJobCommand --> LogicManager : result
+deactivate EditJobCommand
+
+EditJobCommand -[hidden]-> LogicManager
+destroy EditJobCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
+
diff --git a/docs/diagrams/mail/AddTemplateActivityDiagram.puml b/docs/diagrams/mail/AddTemplateActivityDiagram.puml
new file mode 100644
index 00000000000..a7ccc0ee873
--- /dev/null
+++ b/docs/diagrams/mail/AddTemplateActivityDiagram.puml
@@ -0,0 +1,20 @@
+@startuml
+start
+:User executes add template command;
+:MyCRM parses user inputs;
+if () then ([Invalid inputs])
+ :Throws ParseException;
+ :Displays unsuccessful add template on UI;
+else ([Valid inputs])
+ :Creates new template;
+ if () then ([Duplicate template])
+ :Throws CommandException;
+ :Displays unsuccessful add template on UI;
+ else ([Else])
+ :Stores template into MyCRM;
+ :Displays successful add template on UI;
+ endif
+endif
+stop
+
+@enduml
diff --git a/docs/diagrams/mail/AddTemplateParseSequenceDiagram.puml b/docs/diagrams/mail/AddTemplateParseSequenceDiagram.puml
new file mode 100644
index 00000000000..209a747590e
--- /dev/null
+++ b/docs/diagrams/mail/AddTemplateParseSequenceDiagram.puml
@@ -0,0 +1,68 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":AddTemplateCommandParser" as AddTemplateCommandParser LOGIC_COLOR
+participant "toAdd:AddTemplateCommand" as AddTemplateCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "s:Subject" as Subject MODEL_COLOR
+participant "b:Body" as Body MODEL_COLOR
+participant "t:Template" as Template MODEL_COLOR
+end box
+[-> LogicManager : execute("addTemplate s/Completed b/Dear...")
+activate LogicManager
+
+group sd parse add template
+LogicManager -> MyCrmParser : parseCommand("addTemplate s/Completed b/Dear...")
+activate MyCrmParser
+
+create AddTemplateCommandParser
+MyCrmParser -> AddTemplateCommandParser
+activate AddTemplateCommandParser
+
+AddTemplateCommandParser --> MyCrmParser
+deactivate AddTemplateCommandParser
+
+MyCrmParser -> AddTemplateCommandParser : parse("s/Completed b/Dear...")
+activate AddTemplateCommandParser
+
+create Subject
+AddTemplateCommandParser -> Subject : "Completed"
+activate Subject
+Subject --> AddTemplateCommandParser : s
+deactivate Subject
+
+create Body
+AddTemplateCommandParser -> Body : "Dear..."
+activate Body
+Body --> AddTemplateCommandParser : b
+deactivate Body
+
+create Template
+AddTemplateCommandParser -> Template : s, b
+activate Template
+Template --> AddTemplateCommandParser : t
+deactivate Template
+
+create AddTemplateCommand
+AddTemplateCommandParser -> AddTemplateCommand : t
+activate AddTemplateCommand
+AddTemplateCommand --> AddTemplateCommandParser : toAdd
+deactivate AddTemplateCommand
+
+AddTemplateCommandParser --> MyCrmParser : toAdd
+deactivate AddTemplateCommandParser
+'Hidden arrow to position destroy marker below the end of the activation bar.
+AddTemplateCommandParser -[hidden]-> MyCrmParser
+destroy AddTemplateCommandParser
+
+MyCrmParser --> LogicManager : toAdd
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/mail/AddTemplateSequenceDiagram.puml b/docs/diagrams/mail/AddTemplateSequenceDiagram.puml
new file mode 100644
index 00000000000..2a118072ecf
--- /dev/null
+++ b/docs/diagrams/mail/AddTemplateSequenceDiagram.puml
@@ -0,0 +1,42 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "toAdd:AddTemplateCommand" as AddTemplateCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+[-> LogicManager : execute("addTemplate s/Completed b/Dear...")
+activate LogicManager
+
+ref over LogicManager, AddTemplateCommand : parse add template
+
+LogicManager -> AddTemplateCommand : execute()
+activate AddTemplateCommand
+
+AddTemplateCommand -> Model : addTemplate(toAdd)
+activate Model
+
+Model --> AddTemplateCommand
+deactivate Model
+
+create CommandResult
+AddTemplateCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddTemplateCommand
+deactivate CommandResult
+
+AddTemplateCommand --> LogicManager : result
+deactivate AddTemplateCommand
+
+AddTemplateCommand -[hidden]-> LogicManager
+destroy AddTemplateCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/mail/DeleteTemplateActivityDiagram.puml b/docs/diagrams/mail/DeleteTemplateActivityDiagram.puml
new file mode 100644
index 00000000000..c490a795f48
--- /dev/null
+++ b/docs/diagrams/mail/DeleteTemplateActivityDiagram.puml
@@ -0,0 +1,20 @@
+@startuml
+start
+:User executes delete template command;
+:MyCRM parses user inputs;
+if () then ([Invalid inputs])
+ :Throws ParseException;
+ :Displays unsuccessful delete template on UI;
+else ([Valid inputs])
+ if () then ([Index out of range])
+ :Throws CommandException;
+ :Displays unsuccessful delete template on UI;
+ else ([Else])
+ :Retrieves template to be deleted;
+ :Deletes template from MyCRM;
+ :Displays successful delete template on UI;
+ endif
+endif
+stop
+
+@enduml
diff --git a/docs/diagrams/mail/DeleteTemplateParseSequenceDiagram.puml b/docs/diagrams/mail/DeleteTemplateParseSequenceDiagram.puml
new file mode 100644
index 00000000000..59ca4e4f8b2
--- /dev/null
+++ b/docs/diagrams/mail/DeleteTemplateParseSequenceDiagram.puml
@@ -0,0 +1,47 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":DeleteTemplateCommandParser" as DeleteTemplateCommandParser LOGIC_COLOR
+participant "toDelete:DeleteTemplateCommand" as DeleteTemplateCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("deleteTemplate 1")
+activate LogicManager
+
+group sd parse delete template
+LogicManager -> MyCrmParser : parseCommand("deleteTemplate 1")
+activate MyCrmParser
+
+create DeleteTemplateCommandParser
+MyCrmParser -> DeleteTemplateCommandParser
+activate DeleteTemplateCommandParser
+
+DeleteTemplateCommandParser --> MyCrmParser
+deactivate DeleteTemplateCommandParser
+
+MyCrmParser -> DeleteTemplateCommandParser : parse("1")
+activate DeleteTemplateCommandParser
+
+create DeleteTemplateCommand
+DeleteTemplateCommandParser -> DeleteTemplateCommand : 1
+activate DeleteTemplateCommand
+
+DeleteTemplateCommand --> DeleteTemplateCommandParser : toDelete
+deactivate DeleteTemplateCommand
+
+DeleteTemplateCommandParser --> MyCrmParser : toDelete
+deactivate DeleteTemplateCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+DeleteTemplateCommandParser -[hidden]-> MyCrmParser
+destroy DeleteTemplateCommandParser
+
+MyCrmParser --> LogicManager : toDelete
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/mail/DeleteTemplateSequenceDiagram.puml b/docs/diagrams/mail/DeleteTemplateSequenceDiagram.puml
new file mode 100644
index 00000000000..30ba0d6d403
--- /dev/null
+++ b/docs/diagrams/mail/DeleteTemplateSequenceDiagram.puml
@@ -0,0 +1,54 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "toDelete:DeleteTemplateCommand" as DeleteTemplateCommand LOGIC_COLOR
+participant "lastShownList: List" as ListTemplate LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("deleteTemplate 1")
+activate LogicManager
+
+ref over LogicManager, DeleteTemplateCommand : parse delete template
+
+LogicManager -> DeleteTemplateCommand : execute()
+
+activate DeleteTemplateCommand
+
+DeleteTemplateCommand -> Model : getFilteredTemplateList()
+activate Model
+Model --> DeleteTemplateCommand : lastShownList
+deactivate Model
+
+DeleteTemplateCommand -> ListTemplate : get(index)
+activate ListTemplate
+ListTemplate --> DeleteTemplateCommand : templateToDelete
+deactivate ListTemplate
+
+DeleteTemplateCommand -> Model : deleteTemplate(templateToDelete)
+activate Model
+Model --> DeleteTemplateCommand
+deactivate Model
+
+create CommandResult
+DeleteTemplateCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> DeleteTemplateCommand
+deactivate CommandResult
+
+DeleteTemplateCommand --> LogicManager : result
+deactivate DeleteTemplateCommand
+
+DeleteTemplateCommand -[hidden]-> LogicManager
+destroy DeleteTemplateCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/mail/EditTemplateActivityDiagram.puml b/docs/diagrams/mail/EditTemplateActivityDiagram.puml
new file mode 100644
index 00000000000..ca633f2a955
--- /dev/null
+++ b/docs/diagrams/mail/EditTemplateActivityDiagram.puml
@@ -0,0 +1,26 @@
+@startuml
+start
+:User executes edit template command;
+:MyCRM parses user inputs;
+if () then ([Invalid inputs])
+ :Throws ParseException;
+ :Displays unsuccessful edited template on UI;
+else ([Valid inputs])
+ if () then ([Index out of range])
+ :Throws CommandException;
+ :Displays unsuccessful edit template on UI;
+ else ([Else])
+ :Gets specified template;
+ :Creates edited template;
+ if () then ([Duplicate template])
+ :Throws CommandException;
+ :Displays unsuccessful edit template on UI;
+ else ([Else])
+ :Replaces specified template with edited template;
+ :Displays successful edit template on UI;
+ endif
+ endif
+endif
+stop
+
+@enduml
diff --git a/docs/diagrams/mail/EditTemplateParseSequenceDiagram.puml b/docs/diagrams/mail/EditTemplateParseSequenceDiagram.puml
new file mode 100644
index 00000000000..d16e43a12c3
--- /dev/null
+++ b/docs/diagrams/mail/EditTemplateParseSequenceDiagram.puml
@@ -0,0 +1,60 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":EditTemplateCommandParser" as EditTemplateCommandParser LOGIC_COLOR
+participant "toEdit:EditTemplateCommand" as EditTemplateCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "editTemplateDescriptor:EditTemplateDescriptor" as EditTemplateDescriptor MODEL_COLOR
+end box
+[-> LogicManager : execute("editTemplate 1 s/Completed b/Order...")
+activate LogicManager
+
+group sd parse edit template
+LogicManager -> MyCrmParser : parseCommand("editTemplate 1 s/Completed b/Order...")
+activate MyCrmParser
+
+create EditTemplateCommandParser
+MyCrmParser -> EditTemplateCommandParser
+activate EditTemplateCommandParser
+
+EditTemplateCommandParser --> MyCrmParser
+deactivate EditTemplateCommandParser
+
+MyCrmParser -> EditTemplateCommandParser : parse("1 s/Completed b/Order...")
+activate EditTemplateCommandParser
+
+create EditTemplateDescriptor
+EditTemplateCommandParser -> EditTemplateDescriptor
+activate EditTemplateDescriptor
+EditTemplateDescriptor --> EditTemplateCommandParser : editTemplateDescriptor
+deactivate EditTemplateDescriptor
+
+EditTemplateCommandParser -> EditTemplateDescriptor : setSubject("Completed")
+
+EditTemplateCommandParser -> EditTemplateDescriptor : setBody("Order...)
+
+create EditTemplateCommand
+EditTemplateCommandParser -> EditTemplateCommand : 1, editTemplateDescriptor
+activate EditTemplateCommand
+
+EditTemplateCommand --> EditTemplateCommandParser : toEdit
+deactivate EditTemplateCommand
+
+EditTemplateCommandParser --> MyCrmParser : toEdit
+deactivate EditTemplateCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+EditTemplateCommandParser -[hidden]-> MyCrmParser
+destroy EditTemplateCommandParser
+
+MyCrmParser --> LogicManager : toEdit
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/mail/EditTemplateSequenceDiagram.puml b/docs/diagrams/mail/EditTemplateSequenceDiagram.puml
new file mode 100644
index 00000000000..85dfa7e3251
--- /dev/null
+++ b/docs/diagrams/mail/EditTemplateSequenceDiagram.puml
@@ -0,0 +1,60 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "toEdit:EditTemplateCommand" as EditTemplateCommand LOGIC_COLOR
+participant "lastShownList: List" as ListTemplate LOGIC_COLOR
+participant "**<>**\nTemplate" as Template LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("editTemplate 1 s/Completed b/Order...")
+activate LogicManager
+
+ref over LogicManager, EditTemplateCommand : parse edit template
+
+LogicManager -> EditTemplateCommand : execute()
+
+activate EditTemplateCommand
+
+EditTemplateCommand -> Model : getFilteredTemplateList()
+activate Model
+Model --> EditTemplateCommand : lastShownList
+deactivate Model
+
+EditTemplateCommand -> ListTemplate : get(index)
+activate ListTemplate
+ListTemplate --> EditTemplateCommand : templateToEdit
+deactivate ListTemplate
+
+EditTemplateCommand -> Template : createEditedTemplate(templateToEdit, editTemplateDescriptor)
+activate Template
+Template --> EditTemplateCommand : editedTemplate
+deactivate Template
+
+EditTemplateCommand -> Model : setTemplate(templateToEdit, editedTemplate)
+activate Model
+Model --> EditTemplateCommand
+deactivate Model
+
+create CommandResult
+EditTemplateCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> EditTemplateCommand
+deactivate CommandResult
+
+EditTemplateCommand --> LogicManager : result
+deactivate EditTemplateCommand
+
+EditTemplateCommand -[hidden]-> LogicManager
+destroy EditTemplateCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/mail/FindTemplateActivityDiagram.puml b/docs/diagrams/mail/FindTemplateActivityDiagram.puml
new file mode 100644
index 00000000000..878d1dcca32
--- /dev/null
+++ b/docs/diagrams/mail/FindTemplateActivityDiagram.puml
@@ -0,0 +1,14 @@
+@startuml
+start
+:User executes find template command;
+:MyCRM parses user inputs;
+if () then ([Invalid inputs])
+ :Throws ParseException;
+ :Displays unsuccessful find template on UI;
+else ([Valid inputs])
+ :Creates subject predicate with keywords;
+ :Update template list with new predicate;
+ :Displays successful find template on UI;
+endif
+stop
+@enduml
diff --git a/docs/diagrams/mail/FindTemplateParseSequenceDiagram.puml b/docs/diagrams/mail/FindTemplateParseSequenceDiagram.puml
new file mode 100644
index 00000000000..47546593069
--- /dev/null
+++ b/docs/diagrams/mail/FindTemplateParseSequenceDiagram.puml
@@ -0,0 +1,55 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":FindTemplateCommandParser" as FindTemplateCommandParser LOGIC_COLOR
+participant ":FindTemplateCommand" as FindTemplateCommand LOGIC_COLOR
+participant "subjectKeywords:SubjectContainsKeywordsPredicate" as SubjectContainsKeywordsPredicate LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("findTemplate Completed")
+activate LogicManager
+
+group sd parse find template
+LogicManager -> MyCrmParser : parseCommand("findTemplate Completed")
+activate MyCrmParser
+
+create FindTemplateCommandParser
+MyCrmParser -> FindTemplateCommandParser
+activate FindTemplateCommandParser
+
+FindTemplateCommandParser --> MyCrmParser
+deactivate FindTemplateCommandParser
+
+MyCrmParser -> FindTemplateCommandParser : parse("Completed")
+activate FindTemplateCommandParser
+
+create SubjectContainsKeywordsPredicate
+FindTemplateCommandParser -> SubjectContainsKeywordsPredicate: "Completed"
+activate SubjectContainsKeywordsPredicate
+SubjectContainsKeywordsPredicate --> FindTemplateCommandParser : subjectKeywords
+deactivate SubjectContainsKeywordsPredicate
+
+create FindTemplateCommand
+FindTemplateCommandParser -> FindTemplateCommand : subjectKeywords
+activate FindTemplateCommand
+
+FindTemplateCommand --> FindTemplateCommandParser : toFind
+deactivate FindTemplateCommand
+
+FindTemplateCommandParser --> MyCrmParser: toFind
+deactivate FindTemplateCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+FindTemplateCommandParser -[hidden]-> MyCrmParser
+destroy FindTemplateCommandParser
+
+
+MyCrmParser --> LogicManager: toFind
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/mail/FindTemplateSequenceDiagram.puml b/docs/diagrams/mail/FindTemplateSequenceDiagram.puml
new file mode 100644
index 00000000000..8238fd0b991
--- /dev/null
+++ b/docs/diagrams/mail/FindTemplateSequenceDiagram.puml
@@ -0,0 +1,43 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "toFind:FindTemplateCommand" as FindTemplateCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("findTemplate Completed")
+activate LogicManager
+
+ref over LogicManager, FindTemplateCommand : parse find template
+
+LogicManager -> FindTemplateCommand : execute()
+
+activate FindTemplateCommand
+
+FindTemplateCommand -> Model : updateFilteredTemplateList(subjectKeywords)
+activate Model
+Model --> FindTemplateCommand
+deactivate Model
+
+create CommandResult
+FindTemplateCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> FindTemplateCommand
+deactivate CommandResult
+
+FindTemplateCommand --> LogicManager : result
+deactivate FindTemplateCommand
+
+FindTemplateCommand -[hidden]-> LogicManager
+destroy FindTemplateCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/mail/MailActivityDiagram.puml b/docs/diagrams/mail/MailActivityDiagram.puml
new file mode 100644
index 00000000000..348776ff2e3
--- /dev/null
+++ b/docs/diagrams/mail/MailActivityDiagram.puml
@@ -0,0 +1,27 @@
+@startuml
+start
+:User executes mail command;
+:MyCRM parses user inputs;
+if () then ([Invalid inputs])
+ :Throws ParseException;
+ :Displays unsuccessful construct mail on UI;
+else ([Valid inputs])
+ if () then ([Index out of range])
+ :Throws CommandException;
+ :Displays unsuccessful construct mail on UI;
+ else ([Else])
+ :Gets specified job;
+ if () then ([No email])
+ :Throws CommandException;
+ :Displays unsuccessful construct mail on UI;
+ else ([Else])
+ :Gets specified template;
+ :Creates mail;
+ :Generate mailto URL;
+ :Displays successful construct mail on UI;
+ endif
+ endif
+endif
+stop
+
+@enduml
diff --git a/docs/diagrams/mail/MailParseSequenceDiagram.puml b/docs/diagrams/mail/MailParseSequenceDiagram.puml
new file mode 100644
index 00000000000..d343c0b57ed
--- /dev/null
+++ b/docs/diagrams/mail/MailParseSequenceDiagram.puml
@@ -0,0 +1,47 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":MailCommandParser" as MailCommandParser LOGIC_COLOR
+participant "toEdit:MailCommand" as MailCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("mail j/1 t/1")
+activate LogicManager
+
+group sd parse mail
+LogicManager -> MyCrmParser : parseCommand("mail j/1 t/1")
+activate MyCrmParser
+
+create MailCommandParser
+MyCrmParser -> MailCommandParser
+activate MailCommandParser
+
+MailCommandParser --> MyCrmParser
+deactivate MailCommandParser
+
+MyCrmParser -> MailCommandParser : parse("j/1 t/1")
+activate MailCommandParser
+
+create MailCommand
+MailCommandParser -> MailCommand : jobIndex, templateIndex
+activate MailCommand
+
+MailCommand --> MailCommandParser : toMail
+deactivate MailCommand
+
+MailCommandParser --> MyCrmParser : toMail
+deactivate MailCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+MailCommandParser -[hidden]-> MyCrmParser
+destroy MailCommandParser
+
+MyCrmParser --> LogicManager : toMail
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/mail/MailSequenceDiagram.puml b/docs/diagrams/mail/MailSequenceDiagram.puml
new file mode 100644
index 00000000000..b6d9faa9a09
--- /dev/null
+++ b/docs/diagrams/mail/MailSequenceDiagram.puml
@@ -0,0 +1,72 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "toMail:MailCommand" as MailCommand LOGIC_COLOR
+participant "lastJobList: List" as ListJob LOGIC_COLOR
+participant "lastTemplateList: List" as ListTemplate LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "m:Mail" as Mail MODEL_COLOR
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("mail j/1 t/1")
+activate LogicManager
+
+ref over LogicManager, MailCommand : parse mail
+
+LogicManager -> MailCommand : execute()
+
+activate MailCommand
+
+MailCommand -> Model : getFilteredJobList()
+activate Model
+Model --> MailCommand : lastJobList
+deactivate Model
+
+MailCommand -> Model : getFilteredTemplateList()
+activate Model
+Model --> MailCommand : lastTemplateList
+deactivate Model
+
+MailCommand -> ListJob : get(jobIndex)
+activate ListJob
+ListJob --> MailCommand : jobToMail
+deactivate ListJob
+
+MailCommand -> ListTemplate : get(templateIndex)
+activate ListTemplate
+ListTemplate --> MailCommand : templateToMail
+deactivate ListTemplate
+
+create Mail
+MailCommand -> Mail : jobToMail, templateToMail
+activate Mail
+Mail --> MailCommand : m
+deactivate Mail
+
+MailCommand -> Model : addMail(m)
+activate Model
+Model --> MailCommand
+deactivate Model
+
+create CommandResult
+MailCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> MailCommand
+deactivate CommandResult
+
+MailCommand --> LogicManager : result
+deactivate MailCommand
+
+MailCommand -[hidden]-> LogicManager
+destroy MailCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/mail/MailUrlSequenceDiagram.puml b/docs/diagrams/mail/MailUrlSequenceDiagram.puml
new file mode 100644
index 00000000000..88a8472f05d
--- /dev/null
+++ b/docs/diagrams/mail/MailUrlSequenceDiagram.puml
@@ -0,0 +1,40 @@
+@startuml
+!include ../style.puml
+
+box Model MODEL_COLOR_T1
+participant ":Mail" as Mail MODEL_COLOR
+participant "thisJob:Job" as Job MODEL_COLOR
+participant "thisTemplate:Template" as Template MODEL_COLOR
+participant "**<>**\nMail" as MailClass MODEL_COLOR
+end box
+[-> Mail : constructMail()
+activate Mail
+
+Mail -> Job : getClientEmail()
+activate Job
+Job --> Mail : email
+deactivate Job
+
+Mail -> Template : getSubject()
+activate Template
+Template --> Mail : subject
+deactivate Template
+
+Mail -> Template : getBody()
+activate Template
+Template --> Mail : body
+deactivate Template
+
+Mail -> MailClass : urlEncode(subject)
+activate MailClass
+MailClass --> Mail : encodedSubject
+deactivate MailClass
+
+Mail -> MailClass : urlEncode(body)
+activate MailClass
+MailClass --> Mail : encodedBody
+deactivate MailClass
+
+[<--Mail : "mailto:%s?subject=%s&body=%s", email, encodedSubject, encodedBody
+deactivate Mail
+@enduml
diff --git a/docs/diagrams/product/AddProductActivityDiagram.puml b/docs/diagrams/product/AddProductActivityDiagram.puml
new file mode 100644
index 00000000000..33cec071634
--- /dev/null
+++ b/docs/diagrams/product/AddProductActivityDiagram.puml
@@ -0,0 +1,33 @@
+@startuml
+start
+:User executes add product command;
+:MyCRM parses user inputs;
+if () then ([Invalid inputs])
+ :Throws ParseException;
+ :Displays unsuccessful message;
+else ([Valid inputs])
+ fork
+ :Creates new **Name**;
+ fork again
+ :(Optional)
+ Creates new Type;
+ fork again
+ : (Optional)
+ Creates new Manufacturer;
+ fork again
+ : (Optional)
+ Creates new Description;
+ end fork
+:Creates new product;
+if () then ([Duplicate name])
+ :Throws CommandException;
+ :Displays unsuccessful message;
+else ([else])
+
+ :Stores product into MyCRM;
+ :Displays successful message;
+endif
+endif
+stop
+
+@enduml
diff --git a/docs/diagrams/product/AddProductSequenceDiagram.puml b/docs/diagrams/product/AddProductSequenceDiagram.puml
new file mode 100644
index 00000000000..d1bc589e43d
--- /dev/null
+++ b/docs/diagrams/product/AddProductSequenceDiagram.puml
@@ -0,0 +1,53 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "toAdd:AddProductCommand" as AddProductCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+participant ":StateManager" as StateManager LOGIC_COLOR
+participant "modifiedResult:CommandResult" as CommandResult2 LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+[-> LogicManager : execute("addProduct n/Asus DUAL-GTX1060-O6G t/GPU m/Asus")
+activate LogicManager
+
+ref over LogicManager, AddProductCommand : parse user input
+
+LogicManager -> AddProductCommand : execute()
+activate AddProductCommand
+
+AddProductCommand -> Model : addProduct(toAdd)
+
+create CommandResult
+AddProductCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddProductCommand : c
+deactivate CommandResult
+
+AddProductCommand -> StateManager : handleProduct(toAdd, c)
+activate StateManager
+
+create CommandResult2
+StateManager -> CommandResult2
+activate CommandResult2
+
+CommandResult2 --> StateManager : modifiedResult
+deactivate CommandResult2
+
+StateManager --> AddProductCommand : modifiedResult
+deactivate StateManager
+
+AddProductCommand --> LogicManager : modifiedResult
+deactivate AddProductCommand
+
+AddProductCommand -[hidden]-> LogicManager
+destroy AddProductCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/product/AddProductSequenceDiagram_Parse.puml b/docs/diagrams/product/AddProductSequenceDiagram_Parse.puml
new file mode 100644
index 00000000000..0ed195ff8b9
--- /dev/null
+++ b/docs/diagrams/product/AddProductSequenceDiagram_Parse.puml
@@ -0,0 +1,78 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":AddProductCommandParser" as AddProductCommandParser LOGIC_COLOR
+participant "toAdd:AddProductCommand" as AddProductCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "**<>**\n:ProductName" as ProductName MODEL_COLOR
+participant "**<>**\n:Type" as Type MODEL_COLOR
+participant "**<>**\n:Manufacturer" as Manufacturer MODEL_COLOR
+participant "**<>**\n:Description" as Description MODEL_COLOR
+participant ":Product" as Product MODEL_COLOR
+end box
+[-> LogicManager : execute("addProduct n/Asus DUAL-GTX1060-O6G t/GPU m/Asus")
+activate LogicManager
+
+group sd parse user input
+LogicManager -> MyCrmParser : parseCommand("addProduct n/Asus DUAL-GTX1060-O6G t/GPU m/Asus")
+activate MyCrmParser
+
+create AddProductCommandParser
+MyCrmParser -> AddProductCommandParser
+activate AddProductCommandParser
+
+AddProductCommandParser --> MyCrmParser
+deactivate AddProductCommandParser
+
+MyCrmParser -> AddProductCommandParser : parse("n/Asus DUAL-GTX1060-O6G t/GPU m/Asus")
+activate AddProductCommandParser
+
+AddProductCommandParser -> ProductName : getProductName("Asus DUAL-GTX1060-O6G")
+activate ProductName
+ProductName --> AddProductCommandParser : n
+deactivate ProductName
+
+AddProductCommandParser -> Type : getType("GPU")
+activate Type
+Type --> AddProductCommandParser : t
+deactivate Type
+
+AddProductCommandParser -> Manufacturer : getManufacturer("Asus")
+activate Manufacturer
+Manufacturer --> AddProductCommandParser : m
+deactivate Manufacturer
+
+AddProductCommandParser -> Description : getEmptyDescription()
+activate Description
+Description --> AddProductCommandParser : empty_d
+deactivate Description
+
+create Product
+AddProductCommandParser -> Product : n, t, m, empty_d
+activate Product
+Product --> AddProductCommandParser : p
+deactivate Product
+
+create AddProductCommand
+AddProductCommandParser -> AddProductCommand : p
+activate AddProductCommand
+AddProductCommand --> AddProductCommandParser : toAdd
+deactivate AddProductCommand
+
+AddProductCommandParser --> MyCrmParser : toAdd
+deactivate AddProductCommandParser
+'Hidden arrow to position destroy marker below the end of the activation bar.
+AddProductCommandParser -[hidden]-> MyCrmParser
+destroy AddProductCommandParser
+
+MyCrmParser --> LogicManager : toAdd
+deactivate MyCrmParser
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/product/EditProductActivityDiagram.puml b/docs/diagrams/product/EditProductActivityDiagram.puml
new file mode 100644
index 00000000000..35ae0b79c17
--- /dev/null
+++ b/docs/diagrams/product/EditProductActivityDiagram.puml
@@ -0,0 +1,26 @@
+@startuml
+start
+:User executes edit product command;
+:MyCRM parses user inputs;
+if () then ([Invalid Input])
+ :Throws ParseException;
+ :Displays unsuccessful message;
+else ([Valid Input])
+if () then ([Index out of range])
+ :Throws CommandException;
+ :Displays unsuccessful message;
+else ([else])
+:Gets product to be edited;
+:Creates edited product;
+if () then ([Duplicate product])
+ :Throws CommandException;
+ :Displays unsuccessful message;
+else ([else])
+ :Replaces specific product with edited product;
+ :Updates product references in jobs;
+ :Displays successful message;
+endif
+endif
+endif
+stop
+@enduml
diff --git a/docs/diagrams/product/EditProductSequenceDiagram.puml b/docs/diagrams/product/EditProductSequenceDiagram.puml
new file mode 100644
index 00000000000..fb923688705
--- /dev/null
+++ b/docs/diagrams/product/EditProductSequenceDiagram.puml
@@ -0,0 +1,60 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":EditProductCommand" as EditProductCommand LOGIC_COLOR
+participant ": List" as ListProduct LOGIC_COLOR
+participant "**<>**\nProduct" as Product LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("editProduct 2 n/Asus d/HDMI")
+activate LogicManager
+
+ref over LogicManager, EditProductCommand : parse user input
+
+LogicManager -> EditProductCommand : execute()
+
+activate EditProductCommand
+
+' get product list
+EditProductCommand -> Model : getFilteredProductList()
+activate Model
+Model --> EditProductCommand
+deactivate Model
+
+EditProductCommand -> ListProduct : get(index)
+activate ListProduct
+ListProduct --> EditProductCommand : productToEdit
+deactivate ListProduct
+
+EditProductCommand -> Product : createEditedProduct(productToEdit, editProductDescriptor)
+activate Product
+Product --> EditProductCommand : editedProduct
+deactivate Product
+
+EditProductCommand -> Model : setProduct(productToEdit, editedProduct)
+
+ref over EditProductCommand, Model : Updates product references in jobs
+
+create CommandResult
+EditProductCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> EditProductCommand
+deactivate CommandResult
+
+EditProductCommand --> LogicManager : result
+deactivate EditProductCommand
+
+EditProductCommand -[hidden]-> LogicManager
+destroy EditProductCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/product/EditProductSequenceDiagram_Parse.puml b/docs/diagrams/product/EditProductSequenceDiagram_Parse.puml
new file mode 100644
index 00000000000..3e6f6afa5d0
--- /dev/null
+++ b/docs/diagrams/product/EditProductSequenceDiagram_Parse.puml
@@ -0,0 +1,58 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":EditProductCommandParser" as EditProductCommandParser LOGIC_COLOR
+participant "toEdit:EditProductCommand" as EditProductCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":EditProductDescriptor" as EditProductDescriptor MODEL_COLOR
+end box
+[-> LogicManager : execute("editProduct 2 n/Asus d/DisplayPort, HDMI")
+activate LogicManager
+
+group sd parse user input
+LogicManager -> MyCrmParser : parseCommand("editProduct 2 n/Asus d/DisplayPort, HDMI")
+activate MyCrmParser
+
+create EditProductCommandParser
+MyCrmParser -> EditProductCommandParser
+activate EditProductCommandParser
+
+EditProductCommandParser --> MyCrmParser
+deactivate EditProductCommandParser
+
+MyCrmParser -> EditProductCommandParser : parse("editProduct 2 n/Asus d/DisplayPort, HDMI")
+activate EditProductCommandParser
+
+create EditProductDescriptor
+EditProductCommandParser -> EditProductDescriptor
+activate EditProductDescriptor
+EditProductDescriptor --> EditProductCommandParser : editProductDescriptor
+deactivate EditProductDescriptor
+
+EditProductCommandParser -> EditProductDescriptor : setName("Asus")
+
+EditProductCommandParser -> EditProductDescriptor : setDescription("DisplayPort, HDMI")
+
+create EditProductCommand
+EditProductCommandParser -> EditProductCommand : 2, EditProductDescriptor
+activate EditProductCommand
+
+EditProductCommand --> EditProductCommandParser : toEdit
+deactivate EditProductCommand
+
+EditProductCommandParser --> MyCrmParser : toEdit
+deactivate EditProductCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+EditProductCommandParser -[hidden]-> MyCrmParser
+destroy EditProductCommandParser
+
+MyCrmParser --> LogicManager : toEdit
+deactivate MyCrmParser
+end
+@enduml
diff --git a/docs/diagrams/product/EditProductSequenceDiagram_Sync.puml b/docs/diagrams/product/EditProductSequenceDiagram_Sync.puml
new file mode 100644
index 00000000000..f2eed8b5432
--- /dev/null
+++ b/docs/diagrams/product/EditProductSequenceDiagram_Sync.puml
@@ -0,0 +1,38 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":EditProductCommand" as EditProductCommand LOGIC_COLOR
+participant ":List" as JobList LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+activate EditProductCommand
+EditProductCommand -> Model : setProduct(productToEdit, editedProduct)
+group sd Updates product references in jobs
+
+
+EditProductCommand -> Model : getLatestJobPredicate()
+activate Model
+Model --> EditProductCommand : latestJobPredicate
+deactivate Model
+
+EditProductCommand -> Model : updateFilteredJobList(PREDICATE_SHOW_ALL_JOBS)
+
+' get job list
+EditProductCommand -> Model : getFilteredJobList()
+activate Model
+Model --> EditProductCommand
+deactivate Model
+
+loop for every job in JobList
+opt the job links to productToEdit
+EditProductCommand -> JobList : setProduct(editedProduct)
+end
+end
+
+EditProductCommand -> Model : updateFilteredJobList(latestJobPredicate)
+end
+@enduml
diff --git a/docs/diagrams/report/ExportReportActivityDiagram.puml b/docs/diagrams/report/ExportReportActivityDiagram.puml
new file mode 100644
index 00000000000..163dee87be3
--- /dev/null
+++ b/docs/diagrams/report/ExportReportActivityDiagram.puml
@@ -0,0 +1,23 @@
+@startuml
+'https://plantuml.com/activity-diagram-beta
+
+start
+:User executes export report command;
+:MyCRM parses user inputs;
+if () then ([Invalid inputs])
+ :Throws ParseException;
+ :Displays unsuccessful message;
+else ([Valid inputs])
+ if () then ([Report window is generated])
+ :Gets job report information from report window;
+ :Export job report;
+ :Displays successful message;
+ else ([else])
+ :Generates a job report;
+ :Export job report;
+ :Displays successful message;
+ endif
+endif
+stop
+
+@enduml
diff --git a/docs/diagrams/report/ExportReportParserSequenceDiagram.puml b/docs/diagrams/report/ExportReportParserSequenceDiagram.puml
new file mode 100644
index 00000000000..b3df32435c8
--- /dev/null
+++ b/docs/diagrams/report/ExportReportParserSequenceDiagram.puml
@@ -0,0 +1,58 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":PrintReportCommandParser" as PrintReportCommandParser LOGIC_COLOR
+participant ":PrintReportCommand" as PrintReportCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("exportReport")
+activate LogicManager
+
+group sd export report
+LogicManager -> MyCrmParser : parseCommand("exportReport")
+activate MyCrmParser
+
+MyCrmParser -> PrintReportCommandParser : parse("exportReport")
+activate PrintReportCommandParser
+
+create PrintReportCommand
+PrintReportCommandParser -> PrintReportCommand
+activate PrintReportCommand
+
+PrintReportCommand --> PrintReportCommandParser :
+deactivate PrintReportCommand
+
+PrintReportCommandParser --> MyCrmParser
+deactivate PrintReportCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+PrintReportCommandParser -[hidden]-> MyCrmParser
+destroy PrintReportCommandParser
+
+MyCrmParser --> LogicManager :
+deactivate MyCrmParser
+
+LogicManager -> PrintReportCommand : execute()
+activate PrintReportCommand
+
+create CommandResult
+PrintReportCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> PrintReportCommand
+deactivate CommandResult
+
+PrintReportCommand --> LogicManager : result
+deactivate PrintReportCommand
+
+PrintReportCommand -[hidden]-> LogicManager
+destroy PrintReportCommand
+
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/report/ExportReportSequenceDiagram.puml b/docs/diagrams/report/ExportReportSequenceDiagram.puml
new file mode 100644
index 00000000000..7361be22062
--- /dev/null
+++ b/docs/diagrams/report/ExportReportSequenceDiagram.puml
@@ -0,0 +1,63 @@
+@startuml
+!include ../style.puml
+
+box UI UI_COLOR_T1
+participant ":MainWindow" as MainWindow UI_COLOR
+participant "ReportWindow" as ReportWindow UI_COLOR
+participant "nodePrinter:NodePrinter" as NodePrinter UI_COLOR
+participant "**<>**\nPrintJob" as PrintJob UI_COLOR
+participant ":Printable" as Printable UI_COLOR
+end box
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":PrintReportCommand" as PrintReportCommand LOGIC_COLOR
+end box
+
+[-> MainWindow : executeCommand("exportReport")
+activate MainWindow
+
+MainWindow -> LogicManager : execute("exportReport")
+activate LogicManager
+
+ref over LogicManager, PrintReportCommand : parse export report
+
+
+LogicManager --> MainWindow
+deactivate LogicManager
+
+MainWindow -> ReportWindow : fillInnerParts()
+activate ReportWindow
+
+
+
+ReportWindow --> MainWindow : report
+deactivate ReportWindow
+
+MainWindow -> ReportWindow : handlePrint(report)
+activate ReportWindow
+
+ReportWindow -> PrintJob : createPrinterJob()
+activate PrintJob
+
+PrintJob --> ReportWindow : job
+deactivate PrintJob
+
+ReportWindow -> NodePrinter : print(job, report)
+activate NodePrinter
+
+NodePrinter -> Printable : init(report))
+activate Printable
+
+Printable --> NodePrinter : printable
+deactivate Printable
+
+NodePrinter --> ReportWindow
+deactivate NodePrinter
+
+ReportWindow --> MainWindow
+deactivate ReportWindow
+
+[<-- MainWindow
+deactivate MainWindow
+@enduml
diff --git a/docs/diagrams/report/PrintReportActivityDiagram.puml b/docs/diagrams/report/PrintReportActivityDiagram.puml
new file mode 100644
index 00000000000..2f91f145ac9
--- /dev/null
+++ b/docs/diagrams/report/PrintReportActivityDiagram.puml
@@ -0,0 +1,32 @@
+@startuml
+'https://plantuml.com/activity-diagram-beta
+
+start
+:User executes print report command;
+:MyCRM parses user inputs;
+if () then ([Invalid inputs])
+ :Throws ParseException;
+ :Displays unsuccessful message;
+else ([Valid inputs])
+ if () then ([Report window is generated])
+ :Requests to focus on report window;
+ :Displays successful message;
+ else ([else])
+ fork
+ :Gets monthly completed jobs;
+ fork again
+ :Gets monthly in-progress jobs;
+ fork again
+ :Gets monthly top three products;
+ fork again
+ :Gets monthly revenue;
+ :Creates a bar graph of monthly revenue;
+ end fork
+ :Generates report window;
+ :Shows report window;
+ :Displays successful message;
+ endif
+endif
+stop
+
+@enduml
diff --git a/docs/diagrams/report/PrintReportParserSequenceDiagram.puml b/docs/diagrams/report/PrintReportParserSequenceDiagram.puml
new file mode 100644
index 00000000000..df50ae273b2
--- /dev/null
+++ b/docs/diagrams/report/PrintReportParserSequenceDiagram.puml
@@ -0,0 +1,65 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":MyCrmParser" as MyCrmParser LOGIC_COLOR
+participant ":PrintReportCommandParser" as PrintReportCommandParser LOGIC_COLOR
+participant ":PrintReportCommand" as PrintReportCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("printReport -i")
+activate LogicManager
+
+group sd print report
+LogicManager -> MyCrmParser : parseCommand("printReport -i")
+activate MyCrmParser
+
+create PrintReportCommandParser
+MyCrmParser -> PrintReportCommandParser
+activate PrintReportCommandParser
+
+PrintReportCommandParser --> MyCrmParser
+deactivate PrintReportCommandParser
+
+MyCrmParser -> PrintReportCommandParser : parse("printReport -i")
+activate PrintReportCommandParser
+
+create PrintReportCommand
+PrintReportCommandParser -> PrintReportCommand : "-i"
+activate PrintReportCommand
+
+PrintReportCommand --> PrintReportCommandParser :
+deactivate PrintReportCommand
+
+PrintReportCommandParser --> MyCrmParser
+deactivate PrintReportCommandParser
+
+'Hidden arrow to position destroy marker below the end of the activation bar.
+PrintReportCommandParser -[hidden]-> MyCrmParser
+destroy PrintReportCommandParser
+
+MyCrmParser --> LogicManager :
+deactivate MyCrmParser
+
+LogicManager -> PrintReportCommand : execute()
+activate PrintReportCommand
+
+create CommandResult
+PrintReportCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> PrintReportCommand
+deactivate CommandResult
+
+PrintReportCommand --> LogicManager : result
+deactivate PrintReportCommand
+
+PrintReportCommand -[hidden]-> LogicManager
+destroy PrintReportCommand
+
+end
+
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/report/PrintReportSequenceDiagram.puml b/docs/diagrams/report/PrintReportSequenceDiagram.puml
new file mode 100644
index 00000000000..ca357b5b62d
--- /dev/null
+++ b/docs/diagrams/report/PrintReportSequenceDiagram.puml
@@ -0,0 +1,108 @@
+@startuml
+!include ../style.puml
+
+box UI UI_COLOR_T1
+participant ":MainWindow" as MainWindow UI_COLOR
+participant "ReportWindow" as ReportWindow UI_COLOR
+participant "JobDisplay" as JobDisplay UI_COLOR
+participant ":GraphDisplay" as GraphDisplay UI_COLOR
+end box
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":PrintReportCommand" as PrintReportCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> MainWindow : executeCommand("printReport -i")
+activate MainWindow
+
+MainWindow -> LogicManager : execute("printReport -i")
+activate LogicManager
+
+ref over LogicManager, PrintReportCommand : parse print report
+
+
+LogicManager --> MainWindow
+deactivate LogicManager
+
+MainWindow -> CommandResult : getCommandFlag()
+activate CommandResult
+
+CommandResult --> MainWindow : flag
+deactivate CommandResult
+
+MainWindow -> ReportWindow : fillInnerParts(flag)
+activate ReportWindow
+
+ReportWindow -> JobDisplay : init()
+activate JobDisplay
+
+' first get
+JobDisplay -> LogicManager : getFilteredMonthlyCompletedJobList()
+activate LogicManager
+LogicManager -> Model : getFilteredMonthlyCompletedJobList()
+activate Model
+
+Model --> LogicManager : completedJobList
+deactivate Model
+LogicManager --> JobDisplay : completedJobList
+deactivate LogicManager
+
+' second get
+JobDisplay -> LogicManager : getFilteredIncompleteJobList()
+activate LogicManager
+LogicManager -> Model : getFilteredIncompleteJobList()
+activate Model
+
+Model --> LogicManager : incompletedJobList
+deactivate Model
+LogicManager --> JobDisplay : incompletedJobList
+deactivate LogicManager
+
+' third get
+JobDisplay -> LogicManager : getFilteredTopThreeProductList()
+activate LogicManager
+LogicManager -> Model : getFilteredTopThreeProductList()
+activate Model
+
+Model --> LogicManager : requiredProductList
+deactivate Model
+LogicManager --> JobDisplay : requiredProductList
+deactivate LogicManager
+
+JobDisplay --> ReportWindow
+deactivate JobDisplay
+
+ReportWindow -> GraphDisplay : init()
+activate GraphDisplay
+
+GraphDisplay -> GraphDisplay : getLastFourMonthRevenue(now)
+activate GraphDisplay
+
+' fourth get
+GraphDisplay -> LogicManager : getRevenue(now)
+activate LogicManager
+LogicManager -> Model : getRevenue((now)
+activate Model
+
+Model --> LogicManager : revenue
+deactivate Model
+LogicManager --> GraphDisplay : revenue
+deactivate LogicManager
+
+GraphDisplay --> GraphDisplay
+deactivate GraphDisplay
+GraphDisplay --> ReportWindow
+deactivate GraphDisplay
+
+ReportWindow --> MainWindow
+deactivate ReportWindow
+
+[<-- MainWindow
+deactivate MainWindow
+@enduml
diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png
index 2f1346869d0..29ab0d9e137 100644
Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ
diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png
index 1ec62caa2a5..ff5d0d20b59 100644
Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ
diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png
index fa327b39618..12ca93a9036 100644
Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ
diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png
index c3028aa1cda..f0c177430ce 100644
Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index 39d7aec4b33..ac82cbe80b0 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/ModelContactClassDiagram.png b/docs/images/ModelContactClassDiagram.png
new file mode 100644
index 00000000000..bcc47b27dab
Binary files /dev/null and b/docs/images/ModelContactClassDiagram.png differ
diff --git a/docs/images/ModelJobClassDiagram.png b/docs/images/ModelJobClassDiagram.png
new file mode 100644
index 00000000000..fff23189894
Binary files /dev/null and b/docs/images/ModelJobClassDiagram.png differ
diff --git a/docs/images/ModelMailClassDiagram.png b/docs/images/ModelMailClassDiagram.png
new file mode 100644
index 00000000000..1ed2791f4b2
Binary files /dev/null and b/docs/images/ModelMailClassDiagram.png differ
diff --git a/docs/images/ModelProductClassDiagram.png b/docs/images/ModelProductClassDiagram.png
new file mode 100644
index 00000000000..ba5fda95276
Binary files /dev/null and b/docs/images/ModelProductClassDiagram.png differ
diff --git a/docs/images/ModelTemplateClassDiagram.png b/docs/images/ModelTemplateClassDiagram.png
new file mode 100644
index 00000000000..805d9ce7e68
Binary files /dev/null and b/docs/images/ModelTemplateClassDiagram.png differ
diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png
index 58ad22ce16a..fe59fb9cd45 100644
Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 82c66f8f16e..151de7b490d 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 91488fd1a0f..28281860e8f 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 4bb8b2ce591..349fdec5c6e 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/contact/AddContactActivityDiagram.png b/docs/images/contact/AddContactActivityDiagram.png
new file mode 100644
index 00000000000..fc5d9ac92e1
Binary files /dev/null and b/docs/images/contact/AddContactActivityDiagram.png differ
diff --git a/docs/images/contact/AddContactParseSequenceDiagram.png b/docs/images/contact/AddContactParseSequenceDiagram.png
new file mode 100644
index 00000000000..d3972f68add
Binary files /dev/null and b/docs/images/contact/AddContactParseSequenceDiagram.png differ
diff --git a/docs/images/contact/AddContactSequenceDiagram.png b/docs/images/contact/AddContactSequenceDiagram.png
new file mode 100644
index 00000000000..da0acba2ce9
Binary files /dev/null and b/docs/images/contact/AddContactSequenceDiagram.png differ
diff --git a/docs/images/contact/DeleteContactActivityDiagram.png b/docs/images/contact/DeleteContactActivityDiagram.png
new file mode 100644
index 00000000000..3212475c06f
Binary files /dev/null and b/docs/images/contact/DeleteContactActivityDiagram.png differ
diff --git a/docs/images/contact/DeleteContactParseSequenceDiagram.png b/docs/images/contact/DeleteContactParseSequenceDiagram.png
new file mode 100644
index 00000000000..a37796d57be
Binary files /dev/null and b/docs/images/contact/DeleteContactParseSequenceDiagram.png differ
diff --git a/docs/images/contact/DeleteContactSequenceDiagram.png b/docs/images/contact/DeleteContactSequenceDiagram.png
new file mode 100644
index 00000000000..c50bc685754
Binary files /dev/null and b/docs/images/contact/DeleteContactSequenceDiagram.png differ
diff --git a/docs/images/contact/EditContactActivityDiagram.png b/docs/images/contact/EditContactActivityDiagram.png
new file mode 100644
index 00000000000..5fc0b056ab4
Binary files /dev/null and b/docs/images/contact/EditContactActivityDiagram.png differ
diff --git a/docs/images/contact/EditContactParseSequenceDiagram.png b/docs/images/contact/EditContactParseSequenceDiagram.png
new file mode 100644
index 00000000000..c12210603c8
Binary files /dev/null and b/docs/images/contact/EditContactParseSequenceDiagram.png differ
diff --git a/docs/images/contact/EditContactSequenceDiagram.png b/docs/images/contact/EditContactSequenceDiagram.png
new file mode 100644
index 00000000000..ab8c0b4c150
Binary files /dev/null and b/docs/images/contact/EditContactSequenceDiagram.png differ
diff --git a/docs/images/contact/FindContactActivityDiagram.png b/docs/images/contact/FindContactActivityDiagram.png
new file mode 100644
index 00000000000..86a56270522
Binary files /dev/null and b/docs/images/contact/FindContactActivityDiagram.png differ
diff --git a/docs/images/contact/FindContactParseSequenceDiagram.png b/docs/images/contact/FindContactParseSequenceDiagram.png
new file mode 100644
index 00000000000..1af5469ef4f
Binary files /dev/null and b/docs/images/contact/FindContactParseSequenceDiagram.png differ
diff --git a/docs/images/contact/FindContactSequenceDiagram.png b/docs/images/contact/FindContactSequenceDiagram.png
new file mode 100644
index 00000000000..7591b776fa5
Binary files /dev/null and b/docs/images/contact/FindContactSequenceDiagram.png differ
diff --git a/docs/images/contact/HideContactActivityDiagram.png b/docs/images/contact/HideContactActivityDiagram.png
new file mode 100644
index 00000000000..0e7ababb0da
Binary files /dev/null and b/docs/images/contact/HideContactActivityDiagram.png differ
diff --git a/docs/images/contact/HideContactParserSequenceDiagram.png b/docs/images/contact/HideContactParserSequenceDiagram.png
new file mode 100644
index 00000000000..0c6638187d4
Binary files /dev/null and b/docs/images/contact/HideContactParserSequenceDiagram.png differ
diff --git a/docs/images/contact/HideContactSequenceDiagram.png b/docs/images/contact/HideContactSequenceDiagram.png
new file mode 100644
index 00000000000..ec67c00d06a
Binary files /dev/null and b/docs/images/contact/HideContactSequenceDiagram.png differ
diff --git a/docs/images/contact/ListContactActivityDiagram.png b/docs/images/contact/ListContactActivityDiagram.png
new file mode 100644
index 00000000000..dce07590514
Binary files /dev/null and b/docs/images/contact/ListContactActivityDiagram.png differ
diff --git a/docs/images/contact/ListContactParseSequenceDiagram.png b/docs/images/contact/ListContactParseSequenceDiagram.png
new file mode 100644
index 00000000000..b132cf41817
Binary files /dev/null and b/docs/images/contact/ListContactParseSequenceDiagram.png differ
diff --git a/docs/images/contact/ListContactSequenceDiagram.png b/docs/images/contact/ListContactSequenceDiagram.png
new file mode 100644
index 00000000000..aa0c6147f08
Binary files /dev/null and b/docs/images/contact/ListContactSequenceDiagram.png differ
diff --git a/docs/images/contact/UndoHideContactActivityDiagram.png b/docs/images/contact/UndoHideContactActivityDiagram.png
new file mode 100644
index 00000000000..95a74faee31
Binary files /dev/null and b/docs/images/contact/UndoHideContactActivityDiagram.png differ
diff --git a/docs/images/contact/UndoHideContactParseSequenceDiagram.png b/docs/images/contact/UndoHideContactParseSequenceDiagram.png
new file mode 100644
index 00000000000..3edd3dba002
Binary files /dev/null and b/docs/images/contact/UndoHideContactParseSequenceDiagram.png differ
diff --git a/docs/images/contact/UndoHideContactSequenceDiagram.png b/docs/images/contact/UndoHideContactSequenceDiagram.png
new file mode 100644
index 00000000000..e63994fcff9
Binary files /dev/null and b/docs/images/contact/UndoHideContactSequenceDiagram.png differ
diff --git a/docs/images/contact/ui-add-contact.png b/docs/images/contact/ui-add-contact.png
new file mode 100644
index 00000000000..eb7a63a1230
Binary files /dev/null and b/docs/images/contact/ui-add-contact.png differ
diff --git a/docs/images/contact/ui-delete-contact.png b/docs/images/contact/ui-delete-contact.png
new file mode 100644
index 00000000000..7e409c987cc
Binary files /dev/null and b/docs/images/contact/ui-delete-contact.png differ
diff --git a/docs/images/contact/ui-edit-contact.png b/docs/images/contact/ui-edit-contact.png
new file mode 100644
index 00000000000..6f52e77f318
Binary files /dev/null and b/docs/images/contact/ui-edit-contact.png differ
diff --git a/docs/images/contact/ui-find-contact.png b/docs/images/contact/ui-find-contact.png
new file mode 100644
index 00000000000..050d3cbc44e
Binary files /dev/null and b/docs/images/contact/ui-find-contact.png differ
diff --git a/docs/images/contact/ui-hide-contact.png b/docs/images/contact/ui-hide-contact.png
new file mode 100644
index 00000000000..221feb2da59
Binary files /dev/null and b/docs/images/contact/ui-hide-contact.png differ
diff --git a/docs/images/contact/ui-list-all-contacts.png b/docs/images/contact/ui-list-all-contacts.png
new file mode 100644
index 00000000000..a9cef504f52
Binary files /dev/null and b/docs/images/contact/ui-list-all-contacts.png differ
diff --git a/docs/images/contact/ui-list-not-hidden-contacts.png b/docs/images/contact/ui-list-not-hidden-contacts.png
new file mode 100644
index 00000000000..d18e5594d70
Binary files /dev/null and b/docs/images/contact/ui-list-not-hidden-contacts.png differ
diff --git a/docs/images/contact/ui-undo-hide-contact.png b/docs/images/contact/ui-undo-hide-contact.png
new file mode 100644
index 00000000000..73b9963fef8
Binary files /dev/null and b/docs/images/contact/ui-undo-hide-contact.png differ
diff --git a/docs/images/dhshah1.png b/docs/images/dhshah1.png
new file mode 100644
index 00000000000..8303044feee
Binary files /dev/null and b/docs/images/dhshah1.png differ
diff --git a/docs/images/hangzelin.png b/docs/images/hangzelin.png
new file mode 100644
index 00000000000..42d7d965bdf
Binary files /dev/null and b/docs/images/hangzelin.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/job/AddJobActivityDiagram.png b/docs/images/job/AddJobActivityDiagram.png
new file mode 100644
index 00000000000..daa0e7352e0
Binary files /dev/null and b/docs/images/job/AddJobActivityDiagram.png differ
diff --git a/docs/images/job/AddJobParseSequenceDiagram.png b/docs/images/job/AddJobParseSequenceDiagram.png
new file mode 100644
index 00000000000..5bb4bd3712e
Binary files /dev/null and b/docs/images/job/AddJobParseSequenceDiagram.png differ
diff --git a/docs/images/job/AddJobSequenceDiagram.png b/docs/images/job/AddJobSequenceDiagram.png
new file mode 100644
index 00000000000..b6160ded21f
Binary files /dev/null and b/docs/images/job/AddJobSequenceDiagram.png differ
diff --git a/docs/images/job/AddJobSequenceDiagramShort.png b/docs/images/job/AddJobSequenceDiagramShort.png
new file mode 100644
index 00000000000..6c700c808a4
Binary files /dev/null and b/docs/images/job/AddJobSequenceDiagramShort.png differ
diff --git a/docs/images/job/AssigningContactActivityDiagram.png b/docs/images/job/AssigningContactActivityDiagram.png
new file mode 100644
index 00000000000..3a9f118c194
Binary files /dev/null and b/docs/images/job/AssigningContactActivityDiagram.png differ
diff --git a/docs/images/job/AssigningProductActivtyDiagram.png b/docs/images/job/AssigningProductActivtyDiagram.png
new file mode 100644
index 00000000000..c368795deb9
Binary files /dev/null and b/docs/images/job/AssigningProductActivtyDiagram.png differ
diff --git a/docs/images/job/EditJobActivityDiagram.png b/docs/images/job/EditJobActivityDiagram.png
new file mode 100644
index 00000000000..96c1582263d
Binary files /dev/null and b/docs/images/job/EditJobActivityDiagram.png differ
diff --git a/docs/images/job/EditJobSequenceDiagram.png b/docs/images/job/EditJobSequenceDiagram.png
new file mode 100644
index 00000000000..6b65a197d2b
Binary files /dev/null and b/docs/images/job/EditJobSequenceDiagram.png differ
diff --git a/docs/images/johndoe.png b/docs/images/johndoe.png
deleted file mode 100644
index 1ce7ce16dc8..00000000000
Binary files a/docs/images/johndoe.png and /dev/null differ
diff --git a/docs/images/kimowarui.png b/docs/images/kimowarui.png
new file mode 100644
index 00000000000..e6a007bfe3b
Binary files /dev/null and b/docs/images/kimowarui.png differ
diff --git a/docs/images/mail/AddTemplateActivityDiagram.png b/docs/images/mail/AddTemplateActivityDiagram.png
new file mode 100644
index 00000000000..4da62cc3aac
Binary files /dev/null and b/docs/images/mail/AddTemplateActivityDiagram.png differ
diff --git a/docs/images/mail/AddTemplateParseSequenceDiagram.png b/docs/images/mail/AddTemplateParseSequenceDiagram.png
new file mode 100644
index 00000000000..79008b330ed
Binary files /dev/null and b/docs/images/mail/AddTemplateParseSequenceDiagram.png differ
diff --git a/docs/images/mail/AddTemplateSequenceDiagram.png b/docs/images/mail/AddTemplateSequenceDiagram.png
new file mode 100644
index 00000000000..4f0ecfcf498
Binary files /dev/null and b/docs/images/mail/AddTemplateSequenceDiagram.png differ
diff --git a/docs/images/mail/DeleteTemplateActivityDiagram.png b/docs/images/mail/DeleteTemplateActivityDiagram.png
new file mode 100644
index 00000000000..ebe82850658
Binary files /dev/null and b/docs/images/mail/DeleteTemplateActivityDiagram.png differ
diff --git a/docs/images/mail/DeleteTemplateParseSequenceDiagram.png b/docs/images/mail/DeleteTemplateParseSequenceDiagram.png
new file mode 100644
index 00000000000..19e843c2c7d
Binary files /dev/null and b/docs/images/mail/DeleteTemplateParseSequenceDiagram.png differ
diff --git a/docs/images/mail/DeleteTemplateSequenceDiagram.png b/docs/images/mail/DeleteTemplateSequenceDiagram.png
new file mode 100644
index 00000000000..dc4901d67c5
Binary files /dev/null and b/docs/images/mail/DeleteTemplateSequenceDiagram.png differ
diff --git a/docs/images/mail/EditTemplateActivityDiagram.png b/docs/images/mail/EditTemplateActivityDiagram.png
new file mode 100644
index 00000000000..f6f0c0b8316
Binary files /dev/null and b/docs/images/mail/EditTemplateActivityDiagram.png differ
diff --git a/docs/images/mail/EditTemplateParseSequenceDiagram.png b/docs/images/mail/EditTemplateParseSequenceDiagram.png
new file mode 100644
index 00000000000..d852c5a9c13
Binary files /dev/null and b/docs/images/mail/EditTemplateParseSequenceDiagram.png differ
diff --git a/docs/images/mail/EditTemplateSequenceDiagram.png b/docs/images/mail/EditTemplateSequenceDiagram.png
new file mode 100644
index 00000000000..fbd1ea375c5
Binary files /dev/null and b/docs/images/mail/EditTemplateSequenceDiagram.png differ
diff --git a/docs/images/mail/FindTemplateActivityDiagram.png b/docs/images/mail/FindTemplateActivityDiagram.png
new file mode 100644
index 00000000000..f242216ba37
Binary files /dev/null and b/docs/images/mail/FindTemplateActivityDiagram.png differ
diff --git a/docs/images/mail/FindTemplateParseSequenceDiagram.png b/docs/images/mail/FindTemplateParseSequenceDiagram.png
new file mode 100644
index 00000000000..6fec43bccd4
Binary files /dev/null and b/docs/images/mail/FindTemplateParseSequenceDiagram.png differ
diff --git a/docs/images/mail/FindTemplateSequenceDiagram.png b/docs/images/mail/FindTemplateSequenceDiagram.png
new file mode 100644
index 00000000000..a62188fe746
Binary files /dev/null and b/docs/images/mail/FindTemplateSequenceDiagram.png differ
diff --git a/docs/images/mail/MailActivityDiagram.png b/docs/images/mail/MailActivityDiagram.png
new file mode 100644
index 00000000000..44ac264420f
Binary files /dev/null and b/docs/images/mail/MailActivityDiagram.png differ
diff --git a/docs/images/mail/MailParseSequenceDiagram.png b/docs/images/mail/MailParseSequenceDiagram.png
new file mode 100644
index 00000000000..9f3203098b6
Binary files /dev/null and b/docs/images/mail/MailParseSequenceDiagram.png differ
diff --git a/docs/images/mail/MailSequenceDiagram.png b/docs/images/mail/MailSequenceDiagram.png
new file mode 100644
index 00000000000..4be89018339
Binary files /dev/null and b/docs/images/mail/MailSequenceDiagram.png differ
diff --git a/docs/images/mail/MailUrlSequenceDiagram.png b/docs/images/mail/MailUrlSequenceDiagram.png
new file mode 100644
index 00000000000..571c3d3c30a
Binary files /dev/null and b/docs/images/mail/MailUrlSequenceDiagram.png differ
diff --git a/docs/images/mail/ui-add-template.png b/docs/images/mail/ui-add-template.png
new file mode 100644
index 00000000000..3d20a1d5a75
Binary files /dev/null and b/docs/images/mail/ui-add-template.png differ
diff --git a/docs/images/mail/ui-delete-template.png b/docs/images/mail/ui-delete-template.png
new file mode 100644
index 00000000000..dc63a4d1695
Binary files /dev/null and b/docs/images/mail/ui-delete-template.png differ
diff --git a/docs/images/mail/ui-edit-template.png b/docs/images/mail/ui-edit-template.png
new file mode 100644
index 00000000000..75762296c9f
Binary files /dev/null and b/docs/images/mail/ui-edit-template.png differ
diff --git a/docs/images/mail/ui-find-template.png b/docs/images/mail/ui-find-template.png
new file mode 100644
index 00000000000..90ae28b7000
Binary files /dev/null and b/docs/images/mail/ui-find-template.png differ
diff --git a/docs/images/mail/ui-list-template.png b/docs/images/mail/ui-list-template.png
new file mode 100644
index 00000000000..6dcc553a4e8
Binary files /dev/null and b/docs/images/mail/ui-list-template.png differ
diff --git a/docs/images/mail/ui-mail-application.png b/docs/images/mail/ui-mail-application.png
new file mode 100644
index 00000000000..0f2fa42b29f
Binary files /dev/null and b/docs/images/mail/ui-mail-application.png differ
diff --git a/docs/images/mail/ui-mail.png b/docs/images/mail/ui-mail.png
new file mode 100644
index 00000000000..93adcd1b924
Binary files /dev/null and b/docs/images/mail/ui-mail.png differ
diff --git a/docs/images/ooawagaeri.png b/docs/images/ooawagaeri.png
new file mode 100644
index 00000000000..2107bd77722
Binary files /dev/null and b/docs/images/ooawagaeri.png differ
diff --git a/docs/images/product/AddProductActivityDiagram.png b/docs/images/product/AddProductActivityDiagram.png
new file mode 100644
index 00000000000..9f7c2bc6933
Binary files /dev/null and b/docs/images/product/AddProductActivityDiagram.png differ
diff --git a/docs/images/product/AddProductSequenceDiagram.png b/docs/images/product/AddProductSequenceDiagram.png
new file mode 100644
index 00000000000..a1b7743b9dc
Binary files /dev/null and b/docs/images/product/AddProductSequenceDiagram.png differ
diff --git a/docs/images/product/AddProductSequenceDiagram_Parse.png b/docs/images/product/AddProductSequenceDiagram_Parse.png
new file mode 100644
index 00000000000..64b3c6aea68
Binary files /dev/null and b/docs/images/product/AddProductSequenceDiagram_Parse.png differ
diff --git a/docs/images/product/EditProductActivityDiagram.png b/docs/images/product/EditProductActivityDiagram.png
new file mode 100644
index 00000000000..1809c4e7fcd
Binary files /dev/null and b/docs/images/product/EditProductActivityDiagram.png differ
diff --git a/docs/images/product/EditProductSequenceDiagram.png b/docs/images/product/EditProductSequenceDiagram.png
new file mode 100644
index 00000000000..361ac69cb6a
Binary files /dev/null and b/docs/images/product/EditProductSequenceDiagram.png differ
diff --git a/docs/images/product/EditProductSequenceDiagram_Parse.png b/docs/images/product/EditProductSequenceDiagram_Parse.png
new file mode 100644
index 00000000000..7d7f173f4a5
Binary files /dev/null and b/docs/images/product/EditProductSequenceDiagram_Parse.png differ
diff --git a/docs/images/product/EditProductSequenceDiagram_Sync.png b/docs/images/product/EditProductSequenceDiagram_Sync.png
new file mode 100644
index 00000000000..698978585bb
Binary files /dev/null and b/docs/images/product/EditProductSequenceDiagram_Sync.png differ
diff --git a/docs/images/report/ExportReportActivityDiagram.png b/docs/images/report/ExportReportActivityDiagram.png
new file mode 100644
index 00000000000..5595cf6eac9
Binary files /dev/null and b/docs/images/report/ExportReportActivityDiagram.png differ
diff --git a/docs/images/report/ExportReportParserSequenceDiagram.png b/docs/images/report/ExportReportParserSequenceDiagram.png
new file mode 100644
index 00000000000..53ca0702076
Binary files /dev/null and b/docs/images/report/ExportReportParserSequenceDiagram.png differ
diff --git a/docs/images/report/ExportReportSequenceDiagram.png b/docs/images/report/ExportReportSequenceDiagram.png
new file mode 100644
index 00000000000..df126362fab
Binary files /dev/null and b/docs/images/report/ExportReportSequenceDiagram.png differ
diff --git a/docs/images/report/PrintReportActivityDiagram.png b/docs/images/report/PrintReportActivityDiagram.png
new file mode 100644
index 00000000000..459a9b7fd4b
Binary files /dev/null and b/docs/images/report/PrintReportActivityDiagram.png differ
diff --git a/docs/images/report/PrintReportParserSequenceDiagram.png b/docs/images/report/PrintReportParserSequenceDiagram.png
new file mode 100644
index 00000000000..405c57f6e7d
Binary files /dev/null and b/docs/images/report/PrintReportParserSequenceDiagram.png differ
diff --git a/docs/images/report/PrintReportSequenceDiagram.png b/docs/images/report/PrintReportSequenceDiagram.png
new file mode 100644
index 00000000000..437abebf4f4
Binary files /dev/null and b/docs/images/report/PrintReportSequenceDiagram.png differ
diff --git a/docs/images/ttraveller7.png b/docs/images/ttraveller7.png
new file mode 100644
index 00000000000..39d2a22ed2c
Binary files /dev/null and b/docs/images/ttraveller7.png differ
diff --git a/docs/images/ui-add-product.PNG b/docs/images/ui-add-product.PNG
new file mode 100644
index 00000000000..47623611ccb
Binary files /dev/null and b/docs/images/ui-add-product.PNG differ
diff --git a/docs/images/ui-addJob-success1.jpg b/docs/images/ui-addJob-success1.jpg
new file mode 100644
index 00000000000..27e5ec1f658
Binary files /dev/null and b/docs/images/ui-addJob-success1.jpg differ
diff --git a/docs/images/ui-addJob-success2.jpg b/docs/images/ui-addJob-success2.jpg
new file mode 100644
index 00000000000..2d80c82fb05
Binary files /dev/null and b/docs/images/ui-addJob-success2.jpg differ
diff --git a/docs/images/ui-clear.png b/docs/images/ui-clear.png
new file mode 100644
index 00000000000..b66b91f14e2
Binary files /dev/null and b/docs/images/ui-clear.png differ
diff --git a/docs/images/ui-completeJob-success.jpg b/docs/images/ui-completeJob-success.jpg
new file mode 100644
index 00000000000..9b609e2e8a4
Binary files /dev/null and b/docs/images/ui-completeJob-success.jpg differ
diff --git a/docs/images/ui-completeJobListJob-success.jpg b/docs/images/ui-completeJobListJob-success.jpg
new file mode 100644
index 00000000000..700bb5ccb98
Binary files /dev/null and b/docs/images/ui-completeJobListJob-success.jpg differ
diff --git a/docs/images/ui-delete-product.PNG b/docs/images/ui-delete-product.PNG
new file mode 100644
index 00000000000..e44837309c3
Binary files /dev/null and b/docs/images/ui-delete-product.PNG differ
diff --git a/docs/images/ui-edit-product.PNG b/docs/images/ui-edit-product.PNG
new file mode 100644
index 00000000000..83d106df99f
Binary files /dev/null and b/docs/images/ui-edit-product.PNG differ
diff --git a/docs/images/ui-editJob-success1.jpg b/docs/images/ui-editJob-success1.jpg
new file mode 100644
index 00000000000..b2256557954
Binary files /dev/null and b/docs/images/ui-editJob-success1.jpg differ
diff --git a/docs/images/ui-editJob-success2.jpg b/docs/images/ui-editJob-success2.jpg
new file mode 100644
index 00000000000..9c6fe7d9863
Binary files /dev/null and b/docs/images/ui-editJob-success2.jpg differ
diff --git a/docs/images/ui-find-job.jpg b/docs/images/ui-find-job.jpg
new file mode 100644
index 00000000000..21d323324af
Binary files /dev/null and b/docs/images/ui-find-job.jpg differ
diff --git a/docs/images/ui-find-product.PNG b/docs/images/ui-find-product.PNG
new file mode 100644
index 00000000000..5ad63a3e02a
Binary files /dev/null and b/docs/images/ui-find-product.PNG differ
diff --git a/docs/images/ui-help.png b/docs/images/ui-help.png
new file mode 100644
index 00000000000..2cc5a81ea30
Binary files /dev/null and b/docs/images/ui-help.png differ
diff --git a/docs/images/ui-history.png b/docs/images/ui-history.png
new file mode 100644
index 00000000000..e8f86c54037
Binary files /dev/null and b/docs/images/ui-history.png differ
diff --git a/docs/images/ui-listJob-success.jpg b/docs/images/ui-listJob-success.jpg
new file mode 100644
index 00000000000..784110d148c
Binary files /dev/null and b/docs/images/ui-listJob-success.jpg differ
diff --git a/docs/images/ui-listJobAll-success.jpg b/docs/images/ui-listJobAll-success.jpg
new file mode 100644
index 00000000000..e2e7ef7a7fc
Binary files /dev/null and b/docs/images/ui-listJobAll-success.jpg differ
diff --git a/docs/images/ui-listJobCompleted-success.jpg b/docs/images/ui-listJobCompleted-success.jpg
new file mode 100644
index 00000000000..1893703c3c8
Binary files /dev/null and b/docs/images/ui-listJobCompleted-success.jpg differ
diff --git a/docs/images/ui-page-setup-macos.png b/docs/images/ui-page-setup-macos.png
new file mode 100644
index 00000000000..c16465fc3a5
Binary files /dev/null and b/docs/images/ui-page-setup-macos.png differ
diff --git a/docs/images/ui-page-setup.png b/docs/images/ui-page-setup.png
new file mode 100644
index 00000000000..f7139363749
Binary files /dev/null and b/docs/images/ui-page-setup.png differ
diff --git a/docs/images/ui-print-monthly.png b/docs/images/ui-print-monthly.png
new file mode 100644
index 00000000000..31f48480e7a
Binary files /dev/null and b/docs/images/ui-print-monthly.png differ
diff --git a/docs/images/ui-printer-macos.png b/docs/images/ui-printer-macos.png
new file mode 100644
index 00000000000..11bc0556da3
Binary files /dev/null and b/docs/images/ui-printer-macos.png differ
diff --git a/docs/images/ui-printer.png b/docs/images/ui-printer.png
new file mode 100644
index 00000000000..736137d4ed3
Binary files /dev/null and b/docs/images/ui-printer.png differ
diff --git a/docs/images/ui-theme.PNG b/docs/images/ui-theme.PNG
new file mode 100644
index 00000000000..5c5b9cce9dd
Binary files /dev/null and b/docs/images/ui-theme.PNG differ
diff --git a/docs/images/ui-undoCompleteJob-success.jpg b/docs/images/ui-undoCompleteJob-success.jpg
new file mode 100644
index 00000000000..d930639ca7e
Binary files /dev/null and b/docs/images/ui-undoCompleteJob-success.jpg differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..85f151208b6 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,25 @@
---
layout: page
-title: AddressBook Level-3
+title: MyCRM
---
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
-[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3)
+[![CI Status](https://github.com/AY2122S1-CS2103-T14-3/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2122S1-CS2103-T14-3/tp/actions)
+[![codecov](https://codecov.io/gh/AY2122S1-CS2103-T14-3/tp/branch/master/graph/badge.svg?token=WAM073Y0Q0)](https://codecov.io/gh/AY2122S1-CS2103-T14-3/tp)
![Ui](images/Ui.png)
-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
+**MyCRM is a desktop application for managing client contacts, repair job statuses, and product information.** It has been optimised for use via a Command Line Interface (CLI) while maintaining the benefits of a Graphical User Interface (GUI).
-* 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.
+**Target Audience:** Tech savvy computer repair shop technician. Owns a business repairing computers and laptops,
+actively servicing multiple clients and answering their queries. Services a wide range of models and deals with both
+hardware and software issues. Also has multiple repair-phases which have to be updated to clients.
+
+**If you type quickly**, MyCRM can complete customer relationship management tasks faster than traditional GUI applications.
+
+* If you are interested in using MyCRM, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested about developing MyCRM, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
**Acknowledgements**
-* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
+* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5), [Mailto](https://mailtolink.me/)
diff --git a/docs/team/dhshah1.md b/docs/team/dhshah1.md
new file mode 100644
index 00000000000..9a46e91e036
--- /dev/null
+++ b/docs/team/dhshah1.md
@@ -0,0 +1,93 @@
+---
+layout: page
+title: Dhruv's Project Portfolio Page
+---
+
+### Project: MyCRM
+
+MyCRM is a desktop application for managing client contacts, repair job statuses, and product information that has been optimised for use via a Command Line Interface (CLI) while maintaining the benefits of a Graphical User Interface (GUI). If you type quickly, MyCRM can complete customer relationship management tasks faster than traditional GUI applications.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability to add repair jobs.
+ * What it does: Allows the user to insert new repair jobs into MyCRM. This includes various information that would
+ be useful to keep track for a repair job such as, description, fee, received date, expected completion date,
+ and the job's completion status. The repair job also links with other entities such as contact and product to keep
+ track of the client information and product information.
+ * Justification: Our application's target audience is repair shop technicians and as such the core purpose of our
+ application is to help them keep track of information related to the repair job's they are handling.
+
+
+* **New Feature**: Added the ability to edit jobs.
+ * What it does: Allows the user to edit/modify information about existing repair jobs in MyCRM. The user can select which
+ attributes of the repair job they want to edit.
+ * Justification: This feature allows the user to easily amend information about existing jobs that might have changed or might
+ have been keyed in wrongly, instead of deleting and keying the information again.
+
+
+* **New Feature**: Allow sequential commands to create or edit a job.
+ * What it does: Allows the user to key in all job related information first, before issuing subsequent commands
+ to either assign new or existing contact and/or product to job. Provides functionality to automatically link newly created contacts or products to jobs if the user desires.
+ * Justification: This feature provides a user-friendly and natural way for users to assign contacts and/or products to jobs while being fast.
+ This is in contrast to keying in all information about a job, client and product at once, which would be extremely error-prone. Or if they are assigning
+ an existing contact and/or product they will first have to search for these entities first and remember their index before adding or editing a job.
+ Instead, with sequential commands the user can choose to input job-specific information first. Then they are free to look through or filter the contact
+ and product lists, to see if any existing entities can be reused. They can immediately select them without having to remember their index. Or if they need to
+ create new contact or product entities, these will automatically be linked to the job current being added or edited, without additional hassle.
+
+
+* **New Feature**: Added the ability to mark jobs complete, and also to revert their completion status.
+ * What it does: Allows the user to mark an existing job as complete once the product has been repaired. Jobs marked complete
+ will by default no longer be shown in the job list. Users can also revert the completion status of already completed jobs.
+ * Justification: Allows the user to focus on the in-progress jobs they are currently handling. Reverting the completion status of a job
+ is a fallback mechanism for the user in the case of mistakenly marking a job as complete or if there are issues with the repair and the job
+ needs to be reopened.
+
+
+* **New Feature**: Added the ability to filter job list based on completion status.
+ * What it does: By default the job list will only show in-progress jobs. User can choose to also see only completed jobs
+ or all jobs (completed and in-progress).
+ * Justification: Most of the time users will be mainly concerned with in-progress jobs. However, from time to time, they may
+ need to refer to prior completed jobs, to edit them or to re-look at some information. Filtering the job list by completion status
+ gives them a flexible way to quickly narrow down the type of jobs they wish to see.
+
+
+* **New Feature**: Added the ability to delete existing jobs
+ * What it does: Deletes an existing job's information from MyCRM.
+ * Justification: Helps user's delete away repair jobs that were wrongly keyed in or repair job's that were cancelled.
+
+
+* **New Feature**: Added the ability to filter products by name.
+ * What it does: Allows the user to view only products whose name matches a certain keyword.
+ * Justification: Product information is stored in MyCRM so that it can be reused for future repair jobs with the same product.
+ Filtering the products by name allows users to quickly find out if a certain product's information is stored in MyCRM and can be reused.
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=dhshah1&sort=groupTitle&sortWithin=title&since=2021-09-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=dhshah1&tabRepo=AY2122S1-CS2103-T14-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false)
+
+
+* **Enhancements through tests**:
+ * Wrote tests for job related features to increase coverage from 60.04% to 66.48%
+ (Pull request [#183](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/183)).
+
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for new features `addJob`, `editJob`, `listJob`, `deleteJob`,
+ `completeJob`, `undoCompleteJob`
+ (Pull requests [#47](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/47),
+ [#117](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/117),
+ [#122](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/122),
+ [#169](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/169).)
+ * Developer Guide:
+ * Added implementation documentation for new features and mechanisms such as `StateManager`, `Adding a Job`, `Editing a Job`
+ (Pull Request [#202](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/202))
+ * Updated documentation of logic component
+
+
+* **Community**:
+ * Reported bugs and suggestions for other team (Pull Requests:
+ [#149](https://github.com/AY2122S1-CS2103T-T12-3/tp/issues/149), [#148](https://github.com/AY2122S1-CS2103T-T12-3/tp/issues/148),
+ [#147](https://github.com/AY2122S1-CS2103T-T12-3/tp/issues/147), [#146](https://github.com/AY2122S1-CS2103T-T12-3/tp/issues/146),
+ [#145](https://github.com/AY2122S1-CS2103T-T12-3/tp/issues/145), [#144](https://github.com/AY2122S1-CS2103T-T12-3/tp/issues/144),
+ [#143](https://github.com/AY2122S1-CS2103T-T12-3/tp/issues/143), [#142](https://github.com/AY2122S1-CS2103T-T12-3/tp/issues/142))
diff --git a/docs/team/hangzelin.md b/docs/team/hangzelin.md
new file mode 100644
index 00000000000..a0314998149
--- /dev/null
+++ b/docs/team/hangzelin.md
@@ -0,0 +1,98 @@
+---
+layout: page
+title: HangZelin's Project Portfolio Page
+---
+
+### Project: MyCRM
+
+MyCRM is a desktop application for managing client contacts, repair job statuses, and product information that has been optimised for use via a Command Line Interface (CLI) while maintaining the benefits of a Graphical User Interface (GUI). If you type quickly, MyCRM can complete customer relationship management tasks faster than traditional GUI applications.
+
+Given below are my contributions to the project.
+
+ * **Enhancements to existing features**: Modified old contacts in **AddressBook** to `addContact`, `editContact`, `findContact`, `deleteContact` and `listContact` commands.
+ * What it does: Allows users to make basic operations in **contact** part of **MyCRM**.
+ * Justification: These add, edit, find etc. commands are now viewed as only a part of commands in **MyCRM** system,
+ and served as commands specific towards **contact** part in **MyCRM**. Besides, a contact is no longer viewed as normal
+ friends in addressBook, but a cooperation partner, so information given should be working official.
+ * Credits: These modifications follow the design pattern in **AddressBook**.
+ * **Enhancements to existing features**: Enhanced `addContact` command to support **optional** fields.
+ * What it does: Allows users to enter partial information of a client.
+ * Justification: A client may consider his own privacy issue, thus **MyCRM** should allow a client to conceal
+ part of his/her information.
+ * **Enhancements to existing features**: Enhanced `listContact` command to be able to list **unhidden** or **all** contacts in the list.
+ * Justification: Since there are hidden clients in the list, normal `listContact` only support viewing unhidden
+ contacts in the list. Therefore, a different **listContact** command is needed if a user requires to see all contacts
+ in the list.
+ * **Enhancements to existing features**: Enhanced `deleteContact` command to disable deletion of a contact if it is already linked to a job.
+ * Justification: It is not reasonable if a user requires to delete a client in **MyCRM** when the job
+ linked to this client has not been completed yet.
+
+ * **New Features**: Added `hideContact` and `undoHideContact` command to Contact part of **MyCRM**:
+ * What it does: Allows users to hide unused contacts in MyCRM. Users can type `undoHideContact` to set this client back
+ to **not hidden** state. A hidden contact will have a new tag **hidden**, indicating that this client is currently unused,
+ and not visible in the normal contact list.
+ * Justification: This feature improves the MyCRM's efficiency when accessing clients
+ because a user can prevent himself/herself getting distracted by unused clients.
+ They can also set hidden clients back to visible easily with these two commands.
+ * Highlights: This enhancement affect the visibility of contact list. It requires further changes in
+ other contact commands if users want to view this contact or make operations upon it.
+ * **New Features**: Added `findJob` command to Job part.
+ * What it does: Allows users to find certain jobs via a job's description, client name,
+ product name linked to it or completion status.
+ * Justification: This feature improves the MyCRM's efficiency when accessing jobs. Users can easily locate
+ jobs they hope to seek in the list.
+ * Credits: This new feature follows the design pattern of **AddressBook**'s `find` command, and more keyword types
+ are allowed in `findJob` instead of only the name of a job.
+
+ * **Enhancements in Tests**:
+ * Wrote more test cases for existing features: `addContactCommandParser`, `deleteContactCommand`,
+ `ListContactCommand`, `listContactCommandParser`, and increases code coverage from 54% to 59%
+ (pull requests [\#168](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/168) [\#167](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/167) [\#166](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/166))
+
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=Hang%20Zelin&sort=groupTitle&sortWithin=title&since=2021-09-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=zoom&tabAuthor=HangZelin&tabRepo=AY2122S1-CS2103-T14-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&zFR=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&zA=HangZelin&zR=AY2122S1-CS2103-T14-3%2Ftp%5Bmaster%5D&zACS=212.8258064516129&zS=2021-09-17&zFS=Hang%20Ze%3Bin&zU=2021-11-06&zMG=false&zFTF=commit&zFGS=groupByRepos)
+
+
+* **Project management**:
+ * Set up the GitHub team org/repo for T14-3.
+ * Set up CodeCov report for team repo.
+ * General code enhancement:
+ * Rename product from `addressBook` to `MyCRM`.
+ * Change product icon to `MyCRM`.
+ * Managed reviewing and merging of most PRs.
+ * Managed code quality review.
+ * Managed releases `v1.2.1` (1 release) on GitHub
+
+
+* **Documentation**:
+ * Set up [MyCRM Product Website](https://ay2122s1-cs2103-t14-3.github.io/tp/).
+ * Update **README** to link to **MyCRM** project
+ * User Guide:
+ * Added documentation for [contacts features](https://ay2122s1-cs2103-t14-3.github.io/tp/UserGuide.html#adding-a-contact-addcontact):
+ * `addContact`: adds a new client's contact into MyCRM.
+ * `editContact`: edits a current client's contact info.
+ * `deleteContact`: deletes a client's contact.
+ * `findContact`: finds all contacts matches keyword given.
+ * `listContact`: lists unhidden contacts or all contacts.
+ * `hideContact`: hides a currently not being used contact info.
+ * `undoHideContact`: undoes hiding a contact operation.
+ * Did cosmetic tweaks to [Quick Start](https://ay2122s1-cs2103-t14-3.github.io/tp/UserGuide.html#quick-start) part command examples.
+ * Developer Guide:
+ * Added [implementation details](https://ay2122s1-cs2103-t14-3.github.io/tp/DeveloperGuide.html#adding-a-contact) for all
+ **contact** command features:`addContact`, `editContact`, `deleteContact`, `findContact`, `hideContact`, `undoHideContact` and `listContact`.
+ * Added [use cases](https://ay2122s1-cs2103-t14-3.github.io/tp/DeveloperGuide.html#use-cases) for all **contact** command features:`addContact`,
+ `editContact`, `deleteContact`, `findContact`, `hideContact`, `undoHideContact` and `listContact`.
+ (Please scroll down to see contact command features part.)
+ * Added [Instructions for manual testing](https://github.com/AY2122S1-CS2103-T14-3/tp/blob/master/docs/DeveloperGuide.md#appendix-instructions-for-manual-testing) for all **contact** command features: `addContact`, `editContact`, `deleteContact`, `findContact`, `hideContact`, `undoHideContact` and `listContact`.
+
+* **Community**:
+ * Helped changed **completion status of a job** into a new component in Job model.
+ (pull requests: [\#77](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/77))
+
+ * Reported bugs and suggestions for other teams in the class (examples:
+ [120](https://github.com/AY2122S1-CS2103-F09-4/tp/issues/120), [121](https://github.com/AY2122S1-CS2103-F09-4/tp/issues/121),
+ [122](https://github.com/AY2122S1-CS2103-F09-4/tp/issues/122), [123](https://github.com/AY2122S1-CS2103-F09-4/tp/issues/123),
+ [124](https://github.com/AY2122S1-CS2103-F09-4/tp/issues/124), [125](https://github.com/AY2122S1-CS2103-F09-4/tp/issues/125),
+ [126](https://github.com/AY2122S1-CS2103-F09-4/tp/issues/126), [127](https://github.com/AY2122S1-CS2103-F09-4/tp/issues/127),
+ [128](https://github.com/AY2122S1-CS2103-F09-4/tp/issues/128))
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/kimowarui.md b/docs/team/kimowarui.md
new file mode 100644
index 00000000000..022483b324d
--- /dev/null
+++ b/docs/team/kimowarui.md
@@ -0,0 +1,45 @@
+---
+layout: page
+title: Zhang Yubin's Project Portfolio Page
+---
+
+### Project: MyCRM
+
+MyCRM is a desktop application for managing client contacts, repair job statuses, and product information that has been optimised for use via a Command Line Interface (CLI) while maintaining the benefits of a Graphical User Interface (GUI). If you type quickly, MyCRM can complete customer relationship management tasks faster than traditional GUI applications.
+
+Given below are my contributions to the project.
+
+* **New Features**: Added a history command that allows the user to navigate to previous commands using up/down keys and list out all previous commands using `history` command.
+
+
+* **New Features**: Added a `printReport` command to generate a report of all jobs within current month.
+ * What it does: Allows user to review all the jobs in current month by showing the jobs completed within current month
+ , the jobs received within current month, the three most popular products received within current month,
+ and a bar graph showing the last four months revenue in this year and last year in a new window.
+ * Justification: This feature improves MyCRM significantly because a user can review monthly job records and statistics so that user can learn about the month’s performance and analyse how to improve.
+
+
+* **New Features**: Added a `exportReport` command to export a report of all jobs within current month.
+ * What it does: Allows user to export the monthly job report generated by `printReport` command as a PDF format.
+ * Justification: This feature improves MyCRM significantly because a user can either save the job report locally or print it out.
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the feature `history`
+ * Development Guide:
+ * Added use cases of feature `history`, `printReport`, and `exportReport`
+ * Added implementation details for `printReport`
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=authorship&tabAuthor=Kimowarui&tabRepo=AY2122S1-CS2103-T14-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)
+
+
+* **Enhancements to existing features**:
+ * Aligned the Ui theme of report window with main window (Pull requests
+ [#176](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/176)).
+
+
+* **Community**:
+ * Reported bugs and suggestions for other teams. e.g.
+ [79](https://github.com/AY2122S1-CS2103T-W12-2/tp/issues/79),
+ [80](https://github.com/AY2122S1-CS2103T-W12-2/tp/issues/80)
+
diff --git a/docs/team/ooawagaeri.md b/docs/team/ooawagaeri.md
new file mode 100644
index 00000000000..19772921526
--- /dev/null
+++ b/docs/team/ooawagaeri.md
@@ -0,0 +1,93 @@
+---
+layout: page
+title: Timothy Chua's Project Portfolio Page
+---
+
+### Project: MyCRM
+
+MyCRM is a desktop application for managing client contacts, repair job statuses, and product information that has
+been optimised for use via a Command Line Interface (CLI) while maintaining the benefits of a Graphical User
+Interface (GUI). If you type quickly, MyCRM can complete customer relationship management tasks faster than
+traditional GUI applications.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability to add templates.
+ * What it does: Allows the user to insert new email templates.
+ * Justification: This feature improves convenience, allowing which users use to prepare and reuse future email
+ templates. Thus, increasing the speed of users can construct and send out emails to customers.
+ * Highlights:
+ * This enhancement affects the mail command by storing up drafts of future emails.
+ * Templates added allow for `\n` characters, allowing for greater email template customization.
+
+* **New Feature**: Added the ability to delete templates.
+ * What it does: Allows the user to remove an existing email template.
+ * Justification: This feature enables users to delete and remove any of their created templates, for example when
+ no longer relevant or when incorrect fields were inputted.
+
+* **New Feature**: Added an edit template command
+ * What it does: Allows the user to edit an existing email template.
+ * Justification: This feature enables users to amend, correct or improve upon their already created templates.
+ Removes the need for users to delete and create a new template just to change certain fields.
+
+* **New Feature**: Added the ability to see all templates.
+ * What it does: Allows the user to view all email template in system.
+ * Justification: Enables users at a glance, view all existing templates.
+
+* **New Feature**: Added the filter and find templates based on subject header.
+ * What it does: Allows the user to view only email template of interest containing specified word.
+ * Justification: Enables users quickly find specific types of templates to employ in mailing, editing or deleting.
+
+* **New Feature**: Added the ability to construct an email to send on the desktop's mailing application.
+ * What it does: Allows the user to create a new email containing the details of an existing job and template.
+ * Justification: This feature allows users to quickly generate emails for customers of jobs in a single line of
+ text rather than slowly opening the mail application, creating a new email, and keying in manually the required
+ of the client, and other information.
+ * Highlights:
+ * This enhancement has the ability to extract a job's client email, template subject header and body text.
+ * Credit: [2ality](https://2ality.com/2010/12/simple-way-of-sending-emails-in-java.html) for reuse of urlEncode.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=ooawagaeri&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabType=zoom&tabAuthor=ooawagaeri&tabRepo=AY2122S1-CS2103-T14-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&zA=ooawagaeri&zR=AY2122S1-CS2103-T14-3%2Ftp%5Bmaster%5D&zACS=220.43386537126995&zS=2021-09-17&zFS=&zU=2021-11-04&zMG=false&zFTF=commit&zFGS=groupByRepos&zFR=false&until=2021-11-04)
+
+* **Project management**:
+ * Managed group coordination and communication on Telegram.
+ * Managed the timelines and issues of most GitHub milestones.
+ * Managed the allocation, tagging and linkage of most GitHub issues.
+ * Managed the allocation and update of GitHub [project board](https://github.com/AY2122S1-CS2103-T14-3/tp/projects/1).
+ * Managed reviewing and merging of some PRs.
+ * Manage quality control of code, documentation and diagrams.
+
+* **Enhancements to existing features**:
+ * Updated the JSON Storage component for Jobs, Products and Templates
+ (Pull requests [#164](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/164)).
+ * Updated the default generated JSON with MyCRM data
+ (Pull requests [#87](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/87)).
+ * Updated the `help` command with hyperlink / clickable URL.
+ (Pull requests [#87](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/87)).
+ * Added more sample data to `SampleDataUtil`.
+ (Pull requests [#197](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/197), [#87](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/87)).
+ * Wrote additional tests for existing features to increase coverage from 53.92% to 57.32%
+ (Pull requests [#164](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/164)).
+
+* **Documentation**:
+ * User Guide:
+ * Added documentation for the features `mail`, `addTemplate`, `deleteTemplate`, `editTemplate`,
+ `listTemplate`, `findTemplate`, `printReport` and `exportReport`:
+ [#123](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/123),
+ [#112](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/112),
+ [#111](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/111),
+ [#41](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/41).
+ * Did cosmetic tweaks to existing documentation of features `exit`, `help` and `clear`:
+ [#112](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/112).
+ * Developer Guide:
+ * Added documentation for the features `mail`, `addTemplate`, `deleteTemplate`, `editTemplate`,
+ `listTemplate`, `findTemplate`, `help` and update "Launch and shutdown" and "Saving data".
+ * Updated documentation of model components [#192](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/192),
+ [#80](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/80).
+
+* **Community**:
+ * Reported bugs and suggestions for other teams in the class (examples:
+ [211](https://github.com/AY2122S1-CS2103-W14-1/tp/issues/211), [193](https://github.com/AY2122S1-CS2103-W14-1/tp/issues/193),
+ [214](https://github.com/AY2122S1-CS2103-W14-1/tp/issues/214), [203](https://github.com/AY2122S1-CS2103-W14-1/tp/issues/203),
+ [210](https://github.com/AY2122S1-CS2103-W14-1/tp/issues/210), [199](https://github.com/AY2122S1-CS2103-W14-1/tp/issues/199),
+ [201](https://github.com/AY2122S1-CS2103-W14-1/tp/issues/201), [216](https://github.com/AY2122S1-CS2103-W14-1/tp/issues/216)).
diff --git a/docs/team/ttraveller7.md b/docs/team/ttraveller7.md
new file mode 100644
index 00000000000..b386db99983
--- /dev/null
+++ b/docs/team/ttraveller7.md
@@ -0,0 +1,74 @@
+---
+layout: page
+title: ttraveller7's Project Portfolio Page
+---
+
+### Project: MyCRM
+
+MyCRM is a desktop application for managing client contacts, repair job statuses, and product information that has been optimised for use via a Command Line Interface (CLI) while maintaining the benefits of a Graphical User Interface (GUI). If you type quickly, MyCRM can complete customer relationship management tasks faster than traditional GUI applications.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability to add products.
+ * What it does: Allows the user to insert new products.
+ * Justification: This feature increases usefulness of MyCrm, allowing users to refer to the details a product and
+ reuse its information in future jobs.
+
+
+* **New Feature**: Added the ability to delete products.
+ * What it does: Allows the user to remove an existing product.
+ * Justification: This feature enables users to delete and remove any of their created products, for example when
+ a product is less popular in repair jobs.
+
+
+* **New Feature**: Added an edit product command
+ * What it does: Allows the user to edit an existing product.
+ * Justification: This feature allows users to change certain fields of a product without removing it.
+ * Highlights: All product fields except *product name* are optional. This allows the user to store product information
+ in multiple short commands instead of one long command.
For example, the user can key in an `addProduct` command to
+ create a product with *product name*, and then issue several `editProduct` commands to fill in *other fields*.
+
+
+* **New Feature**: Added a list product command.
+ * What it does: Allows the user to view all products in MyCrm.
+ * Justification: Enables users view all existing products.
+
+
+* **New Feature**: Created a new appearance theme and added the ability to change theme.
+ * What it does: Allows the user to change the theme of appearance of MyCrm.
+ * Justification: Allows the user to switch to his/her preferred theme.
+ * Highlights:
+ * A new theme, [light](https://github.com/AY2122S1-CS2103-T14-3/tp/blob/master/src/main/resources/view/LightTheme.css),
+ is created as a stylesheet with new colorscheme and font family.
+ * To change the current theme, the user can either use `themeCommand` or choose from the theme menu
+ * When the user exits MyCrm, the most recent theme will be stored. The same theme will be loaded to GUI next time
+ the user opens the app.
+
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-17&tabOpen=true&tabAuthor=TTraveller7&tabRepo=AY2122S1-CS2103-T14-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&tabType=authorship)
+
+
+* **Enhancements to existing features**:
+ * Modified GUI layout: split the list pane to two columns (Pull requests
+ [#82](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/82)).
+ * Adjusted attributes of Ui components; make GUI response with different commands reasonably (Pull requests
+ [#65](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/65))
+ * Wrote tests for products commands, product components and theme command.
+
+
+* **Documentation**:
+ * Updated README of team repository [#37](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/37)
+ * User Guide: Added documentation for the features `addProduct`, `deleteProduct`, `listProduct`, `editProduct`,
+ `findProduct`, and `theme`.
+ * Developer Guide:
+ * Added use cases and test instructions for the features `addProduct`, `deleteProduct`, `listProduct`, `editProduct`,
+ `findProduct`, and `theme`.
+ * Added implementation details for the features `addProduct` and `editProduct`.
+ * Updated documentation of UI components [#116](https://github.com/AY2122S1-CS2103-T14-3/tp/pull/116).
+
+
+* **Community**:
+ * Reported bugs and suggestions for other teams. e.g.
+ [125](https://github.com/AY2122S1-CS2103T-T17-4/tp/issues/125), [128](https://github.com/AY2122S1-CS2103T-T17-4/tp/issues/128),
+ [129](https://github.com/AY2122S1-CS2103T-T17-4/tp/issues/129), [123](https://github.com/AY2122S1-CS2103T-T17-4/tp/issues/123),
+ [124](https://github.com/AY2122S1-CS2103T-T17-4/tp/issues/124), [127](https://github.com/AY2122S1-CS2103T-T17-4/tp/issues/127).
diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md
index 8919d8eaa17..4257dbf13e6 100644
--- a/docs/tutorials/AddRemark.md
+++ b/docs/tutorials/AddRemark.md
@@ -23,9 +23,9 @@ For now, let’s keep `RemarkCommand` as simple as possible and print some outpu
**`RemarkCommand.java`:**
``` java
-package seedu.address.logic.commands;
+package seedu.mycrm.logic.commands;
-import seedu.address.model.Model;
+import seedu.mycrm.model.Model;
/**
* Changes the remark of an existing person in the address book.
@@ -91,7 +91,7 @@ Let’s change `RemarkCommand` to parse input from the user.
We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if our code is functioning as intended.
``` java
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.mycrm.commons.util.CollectionUtil.requireAllNonNull;
//...
public class RemarkCommand extends Command {
//...
@@ -142,7 +142,7 @@ Your code should look something like [this](https://github.com/se-edu/addressboo
Now let’s move on to writing a parser that will extract the index and remark from the input provided by the user.
-Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package. The class must extend the `Parser` interface.
+Create a `RemarkCommandParser` class in the `seedu.mycrm.logic.parser` package. The class must extend the `Parser` interface.
![The relationship between Parser and RemarkCommandParser](../images/add-remark/ParserInterface.png)
@@ -229,7 +229,7 @@ Now that we have all the information that we need, let’s lay the groundwork fo
### Add a new `Remark` class
-Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code.
+Create a new `Remark` in `seedu.mycrm.model.contact`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code.
A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-af2f075d24dfcd333876f0fbce321f25). Note how `Remark` has no constrains and thus does not require input
validation.
@@ -242,7 +242,7 @@ Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark`
Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person.
-Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-0c6b6abcfac8c205e075294f25e851fe).
+Simply add the following to [`seedu.mycrm.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-0c6b6abcfac8c205e075294f25e851fe).
**`PersonCard.java`:**
diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md
index f29169bc924..a70f2963700 100644
--- a/docs/tutorials/RemovingFields.md
+++ b/docs/tutorials/RemovingFields.md
@@ -28,7 +28,7 @@ IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a re
### Assisted refactoring
-The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
+The `address` field in `Person` is actually an instance of the `seedu.mycrm.model.contact.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
* :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences`
![Usages detected](../images/remove/UnsafeDelete.png)
diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md
index 4fb62a83ef6..8eb50614787 100644
--- a/docs/tutorials/TracingCode.md
+++ b/docs/tutorials/TracingCode.md
@@ -39,7 +39,7 @@ In our case, we would want to begin the tracing at the very point where the App
-According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`.
+According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.mycrm.logic.Logic`.
@@ -48,7 +48,7 @@ According to the sequence diagram you saw earlier (and repeated above for refere
:bulb: **Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`.
-A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for.
+A quick look at the `seedu.mycrm.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for.
```java
public interface Logic {
diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
deleted file mode 100644
index 1deb3a1e469..00000000000
--- a/src/main/java/seedu/address/commons/core/Messages.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package seedu.address.commons.core;
-
-/**
- * Container for user visible messages.
- */
-public class Messages {
-
- public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
- public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
- public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
- public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
-
-}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
deleted file mode 100644
index 92cd8fa605a..00000000000
--- a/src/main/java/seedu/address/logic/Logic.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package seedu.address.logic;
-
-import java.nio.file.Path;
-
-import javafx.collections.ObservableList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
-
-/**
- * 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.
- * @throws CommandException If an error occurs during command execution.
- * @throws ParseException If an error occurs during parsing.
- */
- CommandResult execute(String commandText) throws CommandException, ParseException;
-
- /**
- * Returns the AddressBook.
- *
- * @see seedu.address.model.Model#getAddressBook()
- */
- ReadOnlyAddressBook getAddressBook();
-
- /** Returns an unmodifiable view of the filtered list of persons */
- ObservableList getFilteredPersonList();
-
- /**
- * Returns the user prefs' address book file path.
- */
- Path getAddressBookFilePath();
-
- /**
- * Returns the user prefs' GUI settings.
- */
- GuiSettings getGuiSettings();
-
- /**
- * Set the user prefs' GUI settings.
- */
- void setGuiSettings(GuiSettings guiSettings);
-}
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java
deleted file mode 100644
index 9d9c6d15bdc..00000000000
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package seedu.address.logic;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.logging.Logger;
-
-import javafx.collections.ObservableList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.AddressBookParser;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.Model;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
-import seedu.address.storage.Storage;
-
-/**
- * The main LogicManager of the app.
- */
-public class LogicManager implements Logic {
- public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: ";
- private final Logger logger = LogsCenter.getLogger(LogicManager.class);
-
- private final Model model;
- private final Storage storage;
- private final AddressBookParser addressBookParser;
-
- /**
- * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}.
- */
- public LogicManager(Model model, Storage storage) {
- this.model = model;
- this.storage = storage;
- addressBookParser = new AddressBookParser();
- }
-
- @Override
- public CommandResult execute(String commandText) throws CommandException, ParseException {
- logger.info("----------------[USER COMMAND][" + commandText + "]");
-
- CommandResult commandResult;
- Command command = addressBookParser.parseCommand(commandText);
- 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();
- }
-
- @Override
- public Path getAddressBookFilePath() {
- return model.getAddressBookFilePath();
- }
-
- @Override
- public GuiSettings getGuiSettings() {
- return model.getGuiSettings();
- }
-
- @Override
- public void setGuiSettings(GuiSettings guiSettings) {
- model.setGuiSettings(guiSettings);
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
deleted file mode 100644
index 71656d7c5c8..00000000000
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
-
-/**
- * Adds a person to the address book.
- */
-public class AddCommand extends Command {
-
- public static final String COMMAND_WORD = "add";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
- + "Parameters: "
- + PREFIX_NAME + "NAME "
- + PREFIX_PHONE + "PHONE "
- + PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
- + "Example: " + COMMAND_WORD + " "
- + PREFIX_NAME + "John Doe "
- + PREFIX_PHONE + "98765432 "
- + PREFIX_EMAIL + "johnd@example.com "
- + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
- + PREFIX_TAG + "friends "
- + PREFIX_TAG + "owesMoney";
-
- public static final String MESSAGE_SUCCESS = "New person added: %1$s";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
-
- private final Person toAdd;
-
- /**
- * Creates an AddCommand to add the specified {@code Person}
- */
- public AddCommand(Person person) {
- requireNonNull(person);
- toAdd = person;
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
-
- if (model.hasPerson(toAdd)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
-
- model.addPerson(toAdd);
- return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof AddCommand // instanceof handles nulls
- && toAdd.equals(((AddCommand) other).toAdd));
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
deleted file mode 100644
index 9c86b1fa6e4..00000000000
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
-
-/**
- * Clears the address book.
- */
-public class ClearCommand extends Command {
-
- public static final String COMMAND_WORD = "clear";
- public static final String MESSAGE_SUCCESS = "Address book has been cleared!";
-
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.setAddressBook(new AddressBook());
- return new CommandResult(MESSAGE_SUCCESS);
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
deleted file mode 100644
index 92f900b7916..00000000000
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.Objects;
-
-/**
- * Represents the result of a command execution.
- */
-public class CommandResult {
-
- private final String feedbackToUser;
-
- /** Help information should be shown to the user. */
- private final boolean showHelp;
-
- /** The application should exit. */
- private final boolean exit;
-
- /**
- * Constructs a {@code CommandResult} with the specified fields.
- */
- public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
- this.feedbackToUser = requireNonNull(feedbackToUser);
- this.showHelp = showHelp;
- this.exit = exit;
- }
-
- /**
- * Constructs a {@code CommandResult} with the specified {@code feedbackToUser},
- * and other fields set to their default value.
- */
- public CommandResult(String feedbackToUser) {
- this(feedbackToUser, false, false);
- }
-
- public String getFeedbackToUser() {
- return feedbackToUser;
- }
-
- public boolean isShowHelp() {
- return showHelp;
- }
-
- public boolean isExit() {
- return exit;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof CommandResult)) {
- return false;
- }
-
- CommandResult otherCommandResult = (CommandResult) other;
- return feedbackToUser.equals(otherCommandResult.feedbackToUser)
- && showHelp == otherCommandResult.showHelp
- && exit == otherCommandResult.exit;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(feedbackToUser, showHelp, exit);
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java
deleted file mode 100644
index 02fd256acba..00000000000
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
-
-/**
- * Deletes a person identified using it's displayed index from the address book.
- */
-public class DeleteCommand extends Command {
-
- public static final String COMMAND_WORD = "delete";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Deletes the person identified by the index number used in the displayed person list.\n"
- + "Parameters: INDEX (must be a positive integer)\n"
- + "Example: " + COMMAND_WORD + " 1";
-
- public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
-
- private final Index targetIndex;
-
- public DeleteCommand(Index targetIndex) {
- this.targetIndex = targetIndex;
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
-
- if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
- model.deletePerson(personToDelete);
- return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof DeleteCommand // instanceof handles nulls
- && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java
deleted file mode 100644
index 7e36114902f..00000000000
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ /dev/null
@@ -1,226 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.CollectionUtil;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Edits the details of an existing person in the address book.
- */
-public class EditCommand extends Command {
-
- public static final String COMMAND_WORD = "edit";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified "
- + "by the index number used in the displayed person list. "
- + "Existing values will be overwritten by the input values.\n"
- + "Parameters: INDEX (must be a positive integer) "
- + "[" + PREFIX_NAME + "NAME] "
- + "[" + PREFIX_PHONE + "PHONE] "
- + "[" + PREFIX_EMAIL + "EMAIL] "
- + "[" + PREFIX_ADDRESS + "ADDRESS] "
- + "[" + PREFIX_TAG + "TAG]...\n"
- + "Example: " + COMMAND_WORD + " 1 "
- + PREFIX_PHONE + "91234567 "
- + PREFIX_EMAIL + "johndoe@example.com";
-
- public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s";
- public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
-
- private final Index index;
- private final EditPersonDescriptor editPersonDescriptor;
-
- /**
- * @param index of the person in the filtered person list to edit
- * @param editPersonDescriptor details to edit the person with
- */
- public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
- requireNonNull(index);
- requireNonNull(editPersonDescriptor);
-
- this.index = index;
- this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor);
- }
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List 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
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditCommand)) {
- return false;
- }
-
- // state check
- EditCommand e = (EditCommand) other;
- return index.equals(e.index)
- && editPersonDescriptor.equals(e.editPersonDescriptor);
- }
-
- /**
- * Stores the details to edit the person with. Each non-empty field value will replace the
- * corresponding field value of the person.
- */
- public static class EditPersonDescriptor {
- private Name name;
- private Phone phone;
- private Email email;
- private Address address;
- private Set tags;
-
- public EditPersonDescriptor() {}
-
- /**
- * Copy constructor.
- * A defensive copy of {@code tags} is used internally.
- */
- public EditPersonDescriptor(EditPersonDescriptor toCopy) {
- setName(toCopy.name);
- setPhone(toCopy.phone);
- setEmail(toCopy.email);
- setAddress(toCopy.address);
- setTags(toCopy.tags);
- }
-
- /**
- * Returns true if at least one field is edited.
- */
- public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
- }
-
- public void setName(Name name) {
- this.name = name;
- }
-
- public Optional getName() {
- return Optional.ofNullable(name);
- }
-
- public void setPhone(Phone phone) {
- this.phone = phone;
- }
-
- public Optional getPhone() {
- return Optional.ofNullable(phone);
- }
-
- public void setEmail(Email email) {
- this.email = email;
- }
-
- public Optional getEmail() {
- return Optional.ofNullable(email);
- }
-
- public void setAddress(Address address) {
- this.address = address;
- }
-
- 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;
- }
-
- /**
- * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- * Returns {@code Optional#empty()} if {@code tags} is null.
- */
- public Optional> getTags() {
- return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
- }
-
- @Override
- public boolean equals(Object other) {
- // short circuit if same object
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditPersonDescriptor)) {
- return false;
- }
-
- // state check
- EditPersonDescriptor e = (EditPersonDescriptor) other;
-
- return getName().equals(e.getName())
- && getPhone().equals(e.getPhone())
- && getEmail().equals(e.getEmail())
- && getAddress().equals(e.getAddress())
- && getTags().equals(e.getTags());
- }
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java
deleted file mode 100644
index 3dd85a8ba90..00000000000
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package seedu.address.logic.commands;
-
-import seedu.address.model.Model;
-
-/**
- * Terminates the program.
- */
-public class ExitCommand extends Command {
-
- public static final String COMMAND_WORD = "exit";
-
- public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ...";
-
- @Override
- public CommandResult execute(Model model) {
- return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
deleted file mode 100644
index d6b19b0a0de..00000000000
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-
-import seedu.address.commons.core.Messages;
-import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-
-/**
- * Finds and lists all persons in address book whose name contains any of the argument keywords.
- * Keyword matching is case insensitive.
- */
-public class FindCommand extends Command {
-
- public static final String COMMAND_WORD = "find";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
- + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
- + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
- + "Example: " + COMMAND_WORD + " alice bob charlie";
-
- private final NameContainsKeywordsPredicate predicate;
-
- public FindCommand(NameContainsKeywordsPredicate predicate) {
- this.predicate = predicate;
- }
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(predicate);
- return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof FindCommand // instanceof handles nulls
- && predicate.equals(((FindCommand) other).predicate)); // state check
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
deleted file mode 100644
index 84be6ad2596..00000000000
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-
-import seedu.address.model.Model;
-
-/**
- * Lists all persons in the address book to the user.
- */
-public class ListCommand extends Command {
-
- public static final String COMMAND_WORD = "list";
-
- public static final String MESSAGE_SUCCESS = "Listed all persons";
-
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(MESSAGE_SUCCESS);
- }
-}
diff --git a/src/main/java/seedu/address/logic/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
deleted file mode 100644
index 1e466792b46..00000000000
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.commands.ClearCommand;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.ExitCommand;
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.commands.HelpCommand;
-import seedu.address.logic.commands.ListCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-
-/**
- * Parses user input.
- */
-public class AddressBookParser {
-
- /**
- * Used for initial separation of command word and args.
- */
- private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)");
-
- /**
- * Parses user input into command for execution.
- *
- * @param userInput full user input string
- * @return the command based on the user input
- * @throws ParseException if the user input does not conform the expected format
- */
- public Command parseCommand(String userInput) throws ParseException {
- final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
- if (!matcher.matches()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
- }
-
- final String commandWord = matcher.group("commandWord");
- final String arguments = matcher.group("arguments");
- switch (commandWord) {
-
- case AddCommand.COMMAND_WORD:
- return new AddCommandParser().parse(arguments);
-
- case EditCommand.COMMAND_WORD:
- return new EditCommandParser().parse(arguments);
-
- case DeleteCommand.COMMAND_WORD:
- return new DeleteCommandParser().parse(arguments);
-
- case ClearCommand.COMMAND_WORD:
- return new ClearCommand();
-
- case FindCommand.COMMAND_WORD:
- return new FindCommandParser().parse(arguments);
-
- case ListCommand.COMMAND_WORD:
- return new ListCommand();
-
- case ExitCommand.COMMAND_WORD:
- return new ExitCommand();
-
- case HelpCommand.COMMAND_WORD:
- return new HelpCommand();
-
- default:
- throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
- }
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
deleted file mode 100644
index 75b1a9bf119..00000000000
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package seedu.address.logic.parser;
-
-/**
- * Contains Command Line Interface (CLI) syntax definitions common to multiple commands
- */
-public class CliSyntax {
-
- /* Prefix definitions */
- public static final Prefix PREFIX_NAME = new Prefix("n/");
- public static final Prefix PREFIX_PHONE = new Prefix("p/");
- public static final Prefix PREFIX_EMAIL = new Prefix("e/");
- public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
- public static final Prefix PREFIX_TAG = new Prefix("t/");
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
deleted file mode 100644
index 522b93081cc..00000000000
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-
-/**
- * Parses input arguments and creates a new DeleteCommand object
- */
-public class DeleteCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the DeleteCommand
- * and returns a DeleteCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public DeleteCommand parse(String args) throws ParseException {
- try {
- Index index = ParserUtil.parseIndex(args);
- return new DeleteCommand(index);
- } catch (ParseException pe) {
- throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
- }
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
deleted file mode 100644
index 845644b7dea..00000000000
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package seedu.address.logic.parser;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_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.parser.exceptions.ParseException;
-import seedu.address.model.tag.Tag;
-
-/**
- * Parses input arguments and creates a new EditCommand object
- */
-public class EditCommandParser implements Parser {
-
- /**
- * Parses the given {@code String} of arguments in the context of the EditCommand
- * and returns an EditCommand object for execution.
- * @throws ParseException if the user input does not conform the expected format
- */
- public EditCommand parse(String args) throws ParseException {
- requireNonNull(args);
- ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
-
- Index index;
-
- try {
- index = ParserUtil.parseIndex(argMultimap.getPreamble());
- } catch (ParseException pe) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
- }
-
- EditPersonDescriptor editPersonDescriptor = new 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()));
- }
- parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
-
- 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));
- }
-
-}
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/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
deleted file mode 100644
index 1a943a0781a..00000000000
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package seedu.address.model;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.List;
-
-import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.UniquePersonList;
-
-/**
- * Wraps all data at the address-book level
- * Duplicates are not allowed (by .isSamePerson comparison)
- */
-public class AddressBook implements ReadOnlyAddressBook {
-
- private final UniquePersonList persons;
-
- /*
- * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
- * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html
- *
- * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication
- * among constructors.
- */
- {
- persons = new UniquePersonList();
- }
-
- public AddressBook() {}
-
- /**
- * Creates an AddressBook using the Persons in the {@code toBeCopied}
- */
- public AddressBook(ReadOnlyAddressBook toBeCopied) {
- this();
- resetData(toBeCopied);
- }
-
- //// list overwrite operations
-
- /**
- * Replaces the contents of the person list with {@code persons}.
- * {@code persons} must not contain duplicate persons.
- */
- public void setPersons(List persons) {
- this.persons.setPersons(persons);
- }
-
- /**
- * Resets the existing data of this {@code AddressBook} with {@code newData}.
- */
- public void resetData(ReadOnlyAddressBook newData) {
- requireNonNull(newData);
-
- setPersons(newData.getPersonList());
- }
-
- //// person-level operations
-
- /**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
- */
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return persons.contains(person);
- }
-
- /**
- * Adds a person to the address book.
- * The person must not already exist in the address book.
- */
- public void addPerson(Person p) {
- persons.add(p);
- }
-
- /**
- * Replaces the given person {@code target} in the list with {@code editedPerson}.
- * {@code target} must exist in the address book.
- * The person identity of {@code editedPerson} must not be the same as another existing person in the address book.
- */
- public void setPerson(Person target, Person editedPerson) {
- requireNonNull(editedPerson);
-
- persons.setPerson(target, editedPerson);
- }
-
- /**
- * Removes {@code key} from this {@code AddressBook}.
- * {@code key} must exist in the address book.
- */
- public void removePerson(Person key) {
- persons.remove(key);
- }
-
- //// util methods
-
- @Override
- public String toString() {
- return persons.asUnmodifiableObservableList().size() + " persons";
- // TODO: refine later
- }
-
- @Override
- public ObservableList getPersonList() {
- return persons.asUnmodifiableObservableList();
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof AddressBook // instanceof handles nulls
- && persons.equals(((AddressBook) other).persons));
- }
-
- @Override
- public int hashCode() {
- return persons.hashCode();
- }
-}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
deleted file mode 100644
index d54df471c1f..00000000000
--- a/src/main/java/seedu/address/model/Model.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package seedu.address.model;
-
-import java.nio.file.Path;
-import java.util.function.Predicate;
-
-import javafx.collections.ObservableList;
-import seedu.address.commons.core.GuiSettings;
-import seedu.address.model.person.Person;
-
-/**
- * The API of the Model component.
- */
-public interface Model {
- /** {@code Predicate} that always evaluate to true */
- Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
-
- /**
- * Replaces user prefs data with the data in {@code userPrefs}.
- */
- void setUserPrefs(ReadOnlyUserPrefs userPrefs);
-
- /**
- * Returns the user prefs.
- */
- ReadOnlyUserPrefs getUserPrefs();
-
- /**
- * Returns the user prefs' GUI settings.
- */
- GuiSettings getGuiSettings();
-
- /**
- * Sets the user prefs' GUI settings.
- */
- void setGuiSettings(GuiSettings guiSettings);
-
- /**
- * Returns the user prefs' address book file path.
- */
- Path getAddressBookFilePath();
-
- /**
- * Sets the user prefs' address book file path.
- */
- void setAddressBookFilePath(Path addressBookFilePath);
-
- /**
- * Replaces address book data with the data in {@code addressBook}.
- */
- void setAddressBook(ReadOnlyAddressBook addressBook);
-
- /** Returns the AddressBook */
- ReadOnlyAddressBook getAddressBook();
-
- /**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
- */
- boolean hasPerson(Person person);
-
- /**
- * Deletes the given person.
- * The person must exist in the address book.
- */
- void deletePerson(Person target);
-
- /**
- * Adds the given person.
- * {@code person} must not already exist in the address book.
- */
- void addPerson(Person person);
-
- /**
- * Replaces the given person {@code target} with {@code editedPerson}.
- * {@code target} must exist in the address book.
- * The person identity of {@code editedPerson} must not be the same as another existing person in the address book.
- */
- void setPerson(Person target, Person editedPerson);
-
- /** Returns an unmodifiable view of the filtered person list */
- ObservableList getFilteredPersonList();
-
- /**
- * Updates the filter of the filtered person list to filter by the given {@code predicate}.
- * @throws NullPointerException if {@code predicate} is null.
- */
- void updateFilteredPersonList(Predicate predicate);
-}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
deleted file mode 100644
index 0650c954f5c..00000000000
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package seedu.address.model;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
-import java.nio.file.Path;
-import java.util.function.Predicate;
-import java.util.logging.Logger;
-
-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;
-
-/**
- * Represents the in-memory model of the address book data.
- */
-public class ModelManager implements Model {
- private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
-
- private final AddressBook addressBook;
- private final UserPrefs userPrefs;
- private final FilteredList filteredPersons;
-
- /**
- * Initializes a ModelManager with the given addressBook and userPrefs.
- */
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
- super();
- requireAllNonNull(addressBook, userPrefs);
-
- logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
-
- this.addressBook = new AddressBook(addressBook);
- this.userPrefs = new UserPrefs(userPrefs);
- filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
- }
-
- public ModelManager() {
- this(new AddressBook(), new UserPrefs());
- }
-
- //=========== UserPrefs ==================================================================================
-
- @Override
- public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
- requireNonNull(userPrefs);
- this.userPrefs.resetData(userPrefs);
- }
-
- @Override
- public ReadOnlyUserPrefs getUserPrefs() {
- return userPrefs;
- }
-
- @Override
- public GuiSettings getGuiSettings() {
- return userPrefs.getGuiSettings();
- }
-
- @Override
- public void setGuiSettings(GuiSettings guiSettings) {
- requireNonNull(guiSettings);
- userPrefs.setGuiSettings(guiSettings);
- }
-
- @Override
- public Path getAddressBookFilePath() {
- return userPrefs.getAddressBookFilePath();
- }
-
- @Override
- public void setAddressBookFilePath(Path addressBookFilePath) {
- requireNonNull(addressBookFilePath);
- userPrefs.setAddressBookFilePath(addressBookFilePath);
- }
-
- //=========== AddressBook ================================================================================
-
- @Override
- public void setAddressBook(ReadOnlyAddressBook addressBook) {
- this.addressBook.resetData(addressBook);
- }
-
- @Override
- public ReadOnlyAddressBook getAddressBook() {
- return addressBook;
- }
-
- @Override
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return addressBook.hasPerson(person);
- }
-
- @Override
- public void deletePerson(Person target) {
- addressBook.removePerson(target);
- }
-
- @Override
- public void addPerson(Person person) {
- addressBook.addPerson(person);
- updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- }
-
- @Override
- public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
-
- addressBook.setPerson(target, editedPerson);
- }
-
- //=========== Filtered Person List Accessors =============================================================
-
- /**
- * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of
- * {@code versionedAddressBook}
- */
- @Override
- public ObservableList getFilteredPersonList() {
- return filteredPersons;
- }
-
- @Override
- public void updateFilteredPersonList(Predicate predicate) {
- requireNonNull(predicate);
- filteredPersons.setPredicate(predicate);
- }
-
- @Override
- public boolean equals(Object obj) {
- // short circuit if same object
- if (obj == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(obj instanceof ModelManager)) {
- return false;
- }
-
- // state check
- ModelManager other = (ModelManager) obj;
- return addressBook.equals(other.addressBook)
- && userPrefs.equals(other.userPrefs)
- && filteredPersons.equals(other.filteredPersons);
- }
-
-}
diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
deleted file mode 100644
index 6ddc2cd9a29..00000000000
--- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package seedu.address.model;
-
-import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
-
-/**
- * Unmodifiable view of an address book
- */
-public interface ReadOnlyAddressBook {
-
- /**
- * Returns an unmodifiable view of the persons list.
- * This list will not contain any duplicate persons.
- */
- ObservableList getPersonList();
-
-}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java
deleted file mode 100644
index 60472ca22a0..00000000000
--- a/src/main/java/seedu/address/model/person/Address.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's address in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)}
- */
-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.
- */
- public static final String VALIDATION_REGEX = "[^\\s].*";
-
- public final String value;
-
- /**
- * Constructs an {@code Address}.
- *
- * @param address A valid address.
- */
- public Address(String address) {
- requireNonNull(address);
- checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS);
- value = address;
- }
-
- /**
- * Returns true if a given string is a valid email.
- */
- public static boolean isValidAddress(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public String toString() {
- return value;
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Address // instanceof handles nulls
- && value.equals(((Address) other).value)); // state check
- }
-
- @Override
- public int hashCode() {
- return value.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
deleted file mode 100644
index 79244d71cf7..00000000000
--- a/src/main/java/seedu/address/model/person/Name.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's name in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
- */
-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;
-
- /**
- * Constructs a {@code Name}.
- *
- * @param name A valid name.
- */
- public Name(String name) {
- requireNonNull(name);
- checkArgument(isValidName(name), MESSAGE_CONSTRAINTS);
- fullName = name;
- }
-
- /**
- * Returns true if a given string is a valid name.
- */
- public static boolean isValidName(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
-
- @Override
- public String toString() {
- return fullName;
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Name // instanceof handles nulls
- && fullName.equals(((Name) other).fullName)); // state check
- }
-
- @Override
- public int hashCode() {
- return fullName.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
deleted file mode 100644
index 8ff1d83fe89..00000000000
--- a/src/main/java/seedu/address/model/person/Person.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package seedu.address.model.person;
-
-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;
-
-/**
- * Represents a Person in the address book.
- * Guarantees: details are present and not null, field values are validated, immutable.
- */
-public class Person {
-
- // Identity fields
- private final Name name;
- private final Phone phone;
- private final Email email;
-
- // Data fields
- private final Address address;
- private final Set tags = new HashSet<>();
-
- /**
- * Every field must be present and not null.
- */
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
- this.name = name;
- this.phone = phone;
- this.email = email;
- this.address = address;
- this.tags.addAll(tags);
- }
-
- public Name getName() {
- return name;
- }
-
- public Phone getPhone() {
- return phone;
- }
-
- public Email getEmail() {
- return email;
- }
-
- 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);
- }
-
- /**
- * Returns true if both persons have the same name.
- * This defines a weaker notion of equality between two persons.
- */
- public boolean isSamePerson(Person otherPerson) {
- if (otherPerson == this) {
- return true;
- }
-
- return otherPerson != null
- && otherPerson.getName().equals(getName());
- }
-
- /**
- * Returns true if both persons have the same identity and data fields.
- * This defines a stronger notion of equality between two persons.
- */
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- if (!(other instanceof Person)) {
- return false;
- }
-
- Person otherPerson = (Person) other;
- return otherPerson.getName().equals(getName())
- && otherPerson.getPhone().equals(getPhone())
- && otherPerson.getEmail().equals(getEmail())
- && otherPerson.getAddress().equals(getAddress())
- && otherPerson.getTags().equals(getTags());
- }
-
- @Override
- public int hashCode() {
- // use this method for custom fields hashing instead of implementing your own
- return Objects.hash(name, phone, email, address, tags);
- }
-
- @Override
- public String toString() {
- final StringBuilder builder = new StringBuilder();
- builder.append(getName())
- .append("; Phone: ")
- .append(getPhone())
- .append("; Email: ")
- .append(getEmail())
- .append("; Address: ")
- .append(getAddress());
-
- Set tags = getTags();
- if (!tags.isEmpty()) {
- builder.append("; Tags: ");
- tags.forEach(builder::append);
- }
- return builder.toString();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
deleted file mode 100644
index 872c76b382f..00000000000
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Person's phone number in the address book.
- * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)}
- */
-public class Phone {
-
-
- public static final String MESSAGE_CONSTRAINTS =
- "Phone numbers should only contain numbers, and it should be at least 3 digits long";
- public static final String VALIDATION_REGEX = "\\d{3,}";
- public final String value;
-
- /**
- * Constructs a {@code Phone}.
- *
- * @param phone A valid phone number.
- */
- public Phone(String phone) {
- requireNonNull(phone);
- checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS);
- value = phone;
- }
-
- /**
- * Returns true if a given string is a valid phone number.
- */
- public static boolean isValidPhone(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public String toString() {
- return value;
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Phone // instanceof handles nulls
- && value.equals(((Phone) other).value)); // state check
- }
-
- @Override
- public int hashCode() {
- return value.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
deleted file mode 100644
index 0fee4fe57e6..00000000000
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
-import java.util.Iterator;
-import java.util.List;
-
-import javafx.collections.FXCollections;
-import javafx.collections.ObservableList;
-import seedu.address.model.person.exceptions.DuplicatePersonException;
-import seedu.address.model.person.exceptions.PersonNotFoundException;
-
-/**
- * A list of persons that enforces uniqueness between its elements and does not allow nulls.
- * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of
- * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is
- * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so
- * as to ensure that the person with exactly the same fields will be removed.
- *
- * Supports a minimal set of list operations.
- *
- * @see Person#isSamePerson(Person)
- */
-public class UniquePersonList implements Iterable {
-
- private final ObservableList internalList = FXCollections.observableArrayList();
- private final ObservableList internalUnmodifiableList =
- FXCollections.unmodifiableObservableList(internalList);
-
- /**
- * Returns true if the list contains an equivalent person as the given argument.
- */
- public boolean contains(Person toCheck) {
- requireNonNull(toCheck);
- return internalList.stream().anyMatch(toCheck::isSamePerson);
- }
-
- /**
- * Adds a person to the list.
- * The person must not already exist in the list.
- */
- public void add(Person toAdd) {
- requireNonNull(toAdd);
- if (contains(toAdd)) {
- throw new DuplicatePersonException();
- }
- internalList.add(toAdd);
- }
-
- /**
- * Replaces the person {@code target} in the list with {@code editedPerson}.
- * {@code target} must exist in the list.
- * The person identity of {@code editedPerson} must not be the same as another existing person in the list.
- */
- public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
-
- int index = internalList.indexOf(target);
- if (index == -1) {
- throw new PersonNotFoundException();
- }
-
- if (!target.isSamePerson(editedPerson) && contains(editedPerson)) {
- throw new DuplicatePersonException();
- }
-
- internalList.set(index, editedPerson);
- }
-
- /**
- * Removes the equivalent person from the list.
- * The person must exist in the list.
- */
- public void remove(Person toRemove) {
- requireNonNull(toRemove);
- if (!internalList.remove(toRemove)) {
- throw new PersonNotFoundException();
- }
- }
-
- public void setPersons(UniquePersonList replacement) {
- requireNonNull(replacement);
- internalList.setAll(replacement.internalList);
- }
-
- /**
- * Replaces the contents of this list with {@code persons}.
- * {@code persons} must not contain duplicate persons.
- */
- public void setPersons(List persons) {
- requireAllNonNull(persons);
- if (!personsAreUnique(persons)) {
- throw new DuplicatePersonException();
- }
-
- internalList.setAll(persons);
- }
-
- /**
- * Returns the backing list as an unmodifiable {@code ObservableList}.
- */
- public ObservableList asUnmodifiableObservableList() {
- return internalUnmodifiableList;
- }
-
- @Override
- public Iterator iterator() {
- return internalList.iterator();
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof UniquePersonList // instanceof handles nulls
- && internalList.equals(((UniquePersonList) other).internalList));
- }
-
- @Override
- public int hashCode() {
- return internalList.hashCode();
- }
-
- /**
- * Returns true if {@code persons} contains only unique persons.
- */
- private boolean personsAreUnique(List persons) {
- for (int i = 0; i < persons.size() - 1; i++) {
- for (int j = i + 1; j < persons.size(); j++) {
- if (persons.get(i).isSamePerson(persons.get(j))) {
- return false;
- }
- }
- }
- return true;
- }
-}
diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
deleted file mode 100644
index d7290f59442..00000000000
--- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package seedu.address.model.person.exceptions;
-
-/**
- * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same
- * identity).
- */
-public class DuplicatePersonException extends RuntimeException {
- public DuplicatePersonException() {
- super("Operation would result in duplicate persons");
- }
-}
diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
deleted file mode 100644
index fa764426ca7..00000000000
--- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package seedu.address.model.person.exceptions;
-
-/**
- * Signals that the operation is unable to find the specified person.
- */
-public class PersonNotFoundException extends RuntimeException {}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
deleted file mode 100644
index 1806da4facf..00000000000
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package seedu.address.model.util;
-
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import seedu.address.model.AddressBook;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.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;
-
-/**
- * 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"))
- };
- }
-
- public static ReadOnlyAddressBook getSampleAddressBook() {
- AddressBook sampleAb = new AddressBook();
- for (Person samplePerson : getSamplePersons()) {
- sampleAb.addPerson(samplePerson);
- }
- return sampleAb;
- }
-
- /**
- * Returns a tag set containing the list of strings given.
- */
- public static Set getTagSet(String... strings) {
- return Arrays.stream(strings)
- .map(Tag::new)
- .collect(Collectors.toSet());
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java
deleted file mode 100644
index 4599182b3f9..00000000000
--- a/src/main/java/seedu/address/storage/AddressBookStorage.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package seedu.address.storage;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.model.ReadOnlyAddressBook;
-
-/**
- * Represents a storage for {@link seedu.address.model.AddressBook}.
- */
-public interface AddressBookStorage {
-
- /**
- * Returns the file path of the data file.
- */
- Path getAddressBookFilePath();
-
- /**
- * Returns AddressBook data as a {@link ReadOnlyAddressBook}.
- * Returns {@code Optional.empty()} if storage file is not found.
- * @throws DataConversionException if the data in storage is not in the expected format.
- * @throws IOException if there was any problem when reading from the storage.
- */
- Optional readAddressBook() throws DataConversionException, IOException;
-
- /**
- * @see #getAddressBookFilePath()
- */
- Optional readAddressBook(Path filePath) throws DataConversionException, IOException;
-
- /**
- * Saves the given {@link ReadOnlyAddressBook} to the storage.
- * @param addressBook cannot be null.
- * @throws IOException if there was any problem writing to the file.
- */
- void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
-
- /**
- * @see #saveAddressBook(ReadOnlyAddressBook)
- */
- void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException;
-
-}
diff --git a/src/main/java/seedu/address/storage/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/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
deleted file mode 100644
index dfab9daaa0d..00000000000
--- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package seedu.address.storage;
-
-import static java.util.Objects.requireNonNull;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-import java.util.logging.Logger;
-
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.commons.util.FileUtil;
-import seedu.address.commons.util.JsonUtil;
-import seedu.address.model.ReadOnlyAddressBook;
-
-/**
- * A class to access AddressBook data stored as a json file on the hard disk.
- */
-public class JsonAddressBookStorage implements AddressBookStorage {
-
- private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class);
-
- private Path filePath;
-
- public JsonAddressBookStorage(Path filePath) {
- this.filePath = filePath;
- }
-
- public Path getAddressBookFilePath() {
- return filePath;
- }
-
- @Override
- public Optional readAddressBook() throws DataConversionException {
- return readAddressBook(filePath);
- }
-
- /**
- * Similar to {@link #readAddressBook()}.
- *
- * @param filePath location of the data. Cannot be null.
- * @throws DataConversionException if the file is not in the correct format.
- */
- public Optional readAddressBook(Path filePath) throws DataConversionException {
- requireNonNull(filePath);
-
- Optional jsonAddressBook = JsonUtil.readJsonFile(
- filePath, JsonSerializableAddressBook.class);
- if (!jsonAddressBook.isPresent()) {
- return Optional.empty();
- }
-
- try {
- return Optional.of(jsonAddressBook.get().toModelType());
- } catch (IllegalValueException ive) {
- logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
- throw new DataConversionException(ive);
- }
- }
-
- @Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException {
- saveAddressBook(addressBook, filePath);
- }
-
- /**
- * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}.
- *
- * @param filePath location of the data. Cannot be null.
- */
- public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException {
- requireNonNull(addressBook);
- requireNonNull(filePath);
-
- FileUtil.createIfMissing(filePath);
- JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath);
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
deleted file mode 100644
index 5efd834091d..00000000000
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package seedu.address.storage;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonRootName;
-
-import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.AddressBook;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
-
-/**
- * An Immutable AddressBook that is serializable to JSON format.
- */
-@JsonRootName(value = "addressbook")
-class JsonSerializableAddressBook {
-
- public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
-
- private final List persons = new ArrayList<>();
-
- /**
- * Constructs a {@code JsonSerializableAddressBook} with the given persons.
- */
- @JsonCreator
- public JsonSerializableAddressBook(@JsonProperty("persons") List persons) {
- this.persons.addAll(persons);
- }
-
- /**
- * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use.
- *
- * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}.
- */
- public JsonSerializableAddressBook(ReadOnlyAddressBook source) {
- persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList()));
- }
-
- /**
- * Converts this address book into the model's {@code AddressBook} object.
- *
- * @throws IllegalValueException if there were any data constraints violated.
- */
- public AddressBook toModelType() throws IllegalValueException {
- AddressBook addressBook = new AddressBook();
- for (JsonAdaptedPerson jsonAdaptedPerson : persons) {
- Person person = jsonAdaptedPerson.toModelType();
- if (addressBook.hasPerson(person)) {
- throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON);
- }
- addressBook.addPerson(person);
- }
- return addressBook;
- }
-
-}
diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java
deleted file mode 100644
index beda8bd9f11..00000000000
--- a/src/main/java/seedu/address/storage/Storage.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package seedu.address.storage;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-
-/**
- * API of the Storage component
- */
-public interface Storage extends AddressBookStorage, UserPrefsStorage {
-
- @Override
- Optional readUserPrefs() throws DataConversionException, IOException;
-
- @Override
- void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException;
-
- @Override
- Path getAddressBookFilePath();
-
- @Override
- Optional readAddressBook() throws DataConversionException, IOException;
-
- @Override
- void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
-
-}
diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java
deleted file mode 100644
index 79868290974..00000000000
--- a/src/main/java/seedu/address/storage/StorageManager.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package seedu.address.storage;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Optional;
-import java.util.logging.Logger;
-
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-
-/**
- * Manages storage of AddressBook data in local storage.
- */
-public class StorageManager implements Storage {
-
- private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
- private AddressBookStorage addressBookStorage;
- private UserPrefsStorage userPrefsStorage;
-
- /**
- * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}.
- */
- public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) {
- super();
- this.addressBookStorage = addressBookStorage;
- this.userPrefsStorage = userPrefsStorage;
- }
-
- // ================ UserPrefs methods ==============================
-
- @Override
- public Path getUserPrefsFilePath() {
- return userPrefsStorage.getUserPrefsFilePath();
- }
-
- @Override
- public Optional readUserPrefs() throws DataConversionException, IOException {
- return userPrefsStorage.readUserPrefs();
- }
-
- @Override
- public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException {
- userPrefsStorage.saveUserPrefs(userPrefs);
- }
-
-
- // ================ AddressBook methods ==============================
-
- @Override
- public Path getAddressBookFilePath() {
- return addressBookStorage.getAddressBookFilePath();
- }
-
- @Override
- public Optional readAddressBook() throws DataConversionException, IOException {
- return readAddressBook(addressBookStorage.getAddressBookFilePath());
- }
-
- @Override
- public Optional readAddressBook(Path filePath) throws DataConversionException, IOException {
- logger.fine("Attempting to read data from file: " + filePath);
- return addressBookStorage.readAddressBook(filePath);
- }
-
- @Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException {
- saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath());
- }
-
- @Override
- public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException {
- logger.fine("Attempting to write to data file: " + filePath);
- addressBookStorage.saveAddressBook(addressBook, filePath);
- }
-
-}
diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java
deleted file mode 100644
index 9e75478664b..00000000000
--- a/src/main/java/seedu/address/ui/CommandBox.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package seedu.address.ui;
-
-import javafx.collections.ObservableList;
-import javafx.fxml.FXML;
-import javafx.scene.control.TextField;
-import javafx.scene.layout.Region;
-import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.exceptions.ParseException;
-
-/**
- * The UI component that is responsible for receiving user command inputs.
- */
-public class CommandBox extends UiPart {
-
- public static final String ERROR_STYLE_CLASS = "error";
- private static final String FXML = "CommandBox.fxml";
-
- private final CommandExecutor commandExecutor;
-
- @FXML
- private TextField commandTextField;
-
- /**
- * Creates a {@code CommandBox} with the given {@code CommandExecutor}.
- */
- public CommandBox(CommandExecutor commandExecutor) {
- super(FXML);
- this.commandExecutor = commandExecutor;
- // calls #setStyleToDefault() whenever there is a change to the text of the command box.
- commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault());
- }
-
- /**
- * Handles the Enter button pressed event.
- */
- @FXML
- private void handleCommandEntered() {
- String commandText = commandTextField.getText();
- if (commandText.equals("")) {
- return;
- }
-
- try {
- commandExecutor.execute(commandText);
- commandTextField.setText("");
- } catch (CommandException | ParseException e) {
- setStyleToIndicateCommandFailure();
- }
- }
-
- /**
- * Sets the command box style to use the default style.
- */
- private void setStyleToDefault() {
- commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS);
- }
-
- /**
- * Sets the command box style to indicate a failed command.
- */
- private void setStyleToIndicateCommandFailure() {
- ObservableList styleClass = commandTextField.getStyleClass();
-
- if (styleClass.contains(ERROR_STYLE_CLASS)) {
- return;
- }
-
- styleClass.add(ERROR_STYLE_CLASS);
- }
-
- /**
- * Represents a function that can execute commands.
- */
- @FunctionalInterface
- public interface CommandExecutor {
- /**
- * Executes the command and returns the result.
- *
- * @see seedu.address.logic.Logic#execute(String)
- */
- CommandResult execute(String commandText) throws CommandException, ParseException;
- }
-
-}
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/AppParameters.java b/src/main/java/seedu/mycrm/AppParameters.java
similarity index 93%
rename from src/main/java/seedu/address/AppParameters.java
rename to src/main/java/seedu/mycrm/AppParameters.java
index ab552c398f3..7668f0f8a11 100644
--- a/src/main/java/seedu/address/AppParameters.java
+++ b/src/main/java/seedu/mycrm/AppParameters.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package seedu.mycrm;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -7,8 +7,8 @@
import java.util.logging.Logger;
import javafx.application.Application;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.util.FileUtil;
+import seedu.mycrm.commons.core.LogsCenter;
+import seedu.mycrm.commons.util.FileUtil;
/**
* Represents the parsed command-line parameters given to the application.
diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/mycrm/Main.java
similarity index 97%
rename from src/main/java/seedu/address/Main.java
rename to src/main/java/seedu/mycrm/Main.java
index 052a5068631..a232650e399 100644
--- a/src/main/java/seedu/address/Main.java
+++ b/src/main/java/seedu/mycrm/Main.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package seedu.mycrm;
import javafx.application.Application;
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/mycrm/MainApp.java
similarity index 66%
rename from src/main/java/seedu/address/MainApp.java
rename to src/main/java/seedu/mycrm/MainApp.java
index 4133aaa0151..2559ee26b2e 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/mycrm/MainApp.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package seedu.mycrm;
import java.io.IOException;
import java.nio.file.Path;
@@ -7,36 +7,36 @@
import javafx.application.Application;
import javafx.stage.Stage;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.core.Version;
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.commons.util.ConfigUtil;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.Logic;
-import seedu.address.logic.LogicManager;
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-import seedu.address.model.util.SampleDataUtil;
-import seedu.address.storage.AddressBookStorage;
-import seedu.address.storage.JsonAddressBookStorage;
-import seedu.address.storage.JsonUserPrefsStorage;
-import seedu.address.storage.Storage;
-import seedu.address.storage.StorageManager;
-import seedu.address.storage.UserPrefsStorage;
-import seedu.address.ui.Ui;
-import seedu.address.ui.UiManager;
+import seedu.mycrm.commons.core.Config;
+import seedu.mycrm.commons.core.LogsCenter;
+import seedu.mycrm.commons.core.Version;
+import seedu.mycrm.commons.exceptions.DataConversionException;
+import seedu.mycrm.commons.util.ConfigUtil;
+import seedu.mycrm.commons.util.StringUtil;
+import seedu.mycrm.logic.Logic;
+import seedu.mycrm.logic.LogicManager;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.ModelManager;
+import seedu.mycrm.model.MyCrm;
+import seedu.mycrm.model.ReadOnlyMyCrm;
+import seedu.mycrm.model.ReadOnlyUserPrefs;
+import seedu.mycrm.model.UserPrefs;
+import seedu.mycrm.model.util.SampleDataUtil;
+import seedu.mycrm.storage.JsonMyCrmStorage;
+import seedu.mycrm.storage.JsonUserPrefsStorage;
+import seedu.mycrm.storage.MyCrmStorage;
+import seedu.mycrm.storage.Storage;
+import seedu.mycrm.storage.StorageManager;
+import seedu.mycrm.storage.UserPrefsStorage;
+import seedu.mycrm.ui.Ui;
+import seedu.mycrm.ui.UiManager;
/**
* Runs the application.
*/
public class MainApp extends Application {
- public static final Version VERSION = new Version(0, 2, 0, true);
+ public static final Version VERSION = new Version(1, 4, 1, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
@@ -48,7 +48,7 @@ public class MainApp extends Application {
@Override
public void init() throws Exception {
- logger.info("=============================[ Initializing AddressBook ]===========================");
+ logger.info("=============================[ Initializing MyCrm ]===========================");
super.init();
AppParameters appParameters = AppParameters.parse(getParameters());
@@ -56,8 +56,8 @@ public void init() throws Exception {
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
- AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ MyCrmStorage myCrmStorage = new JsonMyCrmStorage(userPrefs.getMyCrmFilePath());
+ storage = new StorageManager(myCrmStorage, userPrefsStorage);
initLogging(config);
@@ -65,29 +65,29 @@ public void init() throws Exception {
logic = new LogicManager(model, storage);
- ui = new UiManager(logic);
+ ui = new UiManager(logic, getHostServices());
}
/**
- * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found,
- * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book.
+ * Returns a {@code ModelManager} with the data from {@code storage}'s myCrm and {@code userPrefs}.
+ * The data from the sample myCrm will be used instead if {@code storage}'s myCrm is not found,
+ * or an empty myCrm will be used instead if errors occur when reading {@code storage}'s myCrm.
*/
private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
- Optional addressBookOptional;
- ReadOnlyAddressBook initialData;
+ Optional myCrmOptional;
+ ReadOnlyMyCrm initialData;
try {
- addressBookOptional = storage.readAddressBook();
- if (!addressBookOptional.isPresent()) {
- logger.info("Data file not found. Will be starting with a sample AddressBook");
+ myCrmOptional = storage.readMyCrm();
+ if (myCrmOptional.isEmpty()) {
+ logger.info("Data file not found. Will be starting with a sample MyCrm");
}
- initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
+ initialData = myCrmOptional.orElseGet(SampleDataUtil::getSampleMyCrm);
} catch (DataConversionException e) {
- logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ logger.warning("Data file not in the correct format. Will be starting with an empty MyCrm");
+ initialData = new MyCrm();
} catch (IOException e) {
- logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ logger.warning("Problem while reading from the file. Will be starting with an empty MyCrm");
+ initialData = new MyCrm();
}
return new ModelManager(initialData, userPrefs);
@@ -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 MyCrm");
initializedPrefs = new UserPrefs();
}
@@ -167,13 +167,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
@Override
public void start(Stage primaryStage) {
- logger.info("Starting AddressBook " + MainApp.VERSION);
+ logger.info("Starting MyCrm " + MainApp.VERSION);
ui.start(primaryStage);
}
@Override
public void stop() {
- logger.info("============================ [ Stopping Address Book ] =============================");
+ logger.info("============================ [ Stopping myCrm ] =============================");
try {
storage.saveUserPrefs(model.getUserPrefs());
} catch (IOException e) {
diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/mycrm/commons/core/Config.java
similarity index 97%
rename from src/main/java/seedu/address/commons/core/Config.java
rename to src/main/java/seedu/mycrm/commons/core/Config.java
index 91145745521..6b2842d5792 100644
--- a/src/main/java/seedu/address/commons/core/Config.java
+++ b/src/main/java/seedu/mycrm/commons/core/Config.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package seedu.mycrm.commons.core;
import java.nio.file.Path;
import java.nio.file.Paths;
diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/mycrm/commons/core/GuiSettings.java
similarity index 76%
rename from src/main/java/seedu/address/commons/core/GuiSettings.java
rename to src/main/java/seedu/mycrm/commons/core/GuiSettings.java
index ba33653be67..3be635d08a2 100644
--- a/src/main/java/seedu/address/commons/core/GuiSettings.java
+++ b/src/main/java/seedu/mycrm/commons/core/GuiSettings.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package seedu.mycrm.commons.core;
import java.awt.Point;
import java.io.Serializable;
@@ -10,29 +10,34 @@
*/
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 = 720;
+ private static final double DEFAULT_WIDTH = 1080;
private final double windowWidth;
private final double windowHeight;
private final Point windowCoordinates;
+ /** Url of theme stylesheet. */
+ private final String themeUrl;
+
/**
- * Constructs a {@code GuiSettings} with the default height, width and position.
+ * Constructs a {@code GuiSettings} with the default height, width, position and theme.
*/
public GuiSettings() {
windowWidth = DEFAULT_WIDTH;
windowHeight = DEFAULT_HEIGHT;
windowCoordinates = null; // null represent no coordinates
+ themeUrl = null; // null represents no theme has ever been set
}
/**
- * Constructs a {@code GuiSettings} with the specified height, width and position.
+ * Constructs a {@code GuiSettings} with the specified height, width, position and theme.
*/
- public GuiSettings(double windowWidth, double windowHeight, int xPosition, int yPosition) {
+ public GuiSettings(double windowWidth, double windowHeight, int xPosition, int yPosition, String themeUrl) {
this.windowWidth = windowWidth;
this.windowHeight = windowHeight;
windowCoordinates = new Point(xPosition, yPosition);
+ this.themeUrl = themeUrl;
}
public double getWindowWidth() {
@@ -47,6 +52,10 @@ public Point getWindowCoordinates() {
return windowCoordinates != null ? new Point(windowCoordinates) : null;
}
+ public String getThemeUrl() {
+ return themeUrl;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -60,12 +69,13 @@ public boolean equals(Object other) {
return windowWidth == o.windowWidth
&& windowHeight == o.windowHeight
- && Objects.equals(windowCoordinates, o.windowCoordinates);
+ && Objects.equals(windowCoordinates, o.windowCoordinates)
+ && Objects.equals(themeUrl, o.themeUrl);
}
@Override
public int hashCode() {
- return Objects.hash(windowWidth, windowHeight, windowCoordinates);
+ return Objects.hash(windowWidth, windowHeight, windowCoordinates, themeUrl);
}
@Override
@@ -74,6 +84,7 @@ public String toString() {
sb.append("Width : " + windowWidth + "\n");
sb.append("Height : " + windowHeight + "\n");
sb.append("Position : " + windowCoordinates);
+ sb.append("ThemeUrl : " + themeUrl);
return sb.toString();
}
}
diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/mycrm/commons/core/LogsCenter.java
similarity index 97%
rename from src/main/java/seedu/address/commons/core/LogsCenter.java
rename to src/main/java/seedu/mycrm/commons/core/LogsCenter.java
index 431e7185e76..0e3c0b54f80 100644
--- a/src/main/java/seedu/address/commons/core/LogsCenter.java
+++ b/src/main/java/seedu/mycrm/commons/core/LogsCenter.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package seedu.mycrm.commons.core;
import java.io.IOException;
import java.util.Arrays;
@@ -18,7 +18,7 @@
public class LogsCenter {
private static final int MAX_FILE_COUNT = 5;
private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB
- private static final String LOG_FILE = "addressbook.log";
+ private static final String LOG_FILE = "myCrm.log";
private static Level currentLogLevel = Level.INFO;
private static final Logger logger = LogsCenter.getLogger(LogsCenter.class);
private static FileHandler fileHandler;
diff --git a/src/main/java/seedu/mycrm/commons/core/Messages.java b/src/main/java/seedu/mycrm/commons/core/Messages.java
new file mode 100644
index 00000000000..c7eb8c4d7ec
--- /dev/null
+++ b/src/main/java/seedu/mycrm/commons/core/Messages.java
@@ -0,0 +1,34 @@
+package seedu.mycrm.commons.core;
+
+/**
+ * Container for user visible messages.
+ */
+public class Messages {
+
+ public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command!";
+ public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
+ public static final String MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX = "The contact index provided is invalid!";
+ public static final String MESSAGE_INVALID_CONTACT_HIDE_REQUEST = "The contact has already been hidden! "
+ + "Please enter another contact index";
+ public static final String MESSAGE_INVALID_CONTACT_UNDO_HIDE_REQUEST = "The contact is not hidden! Please enter "
+ + "another contact index!";
+ public static final String MESSAGE_INVALID_CONTACT_DELETE_REQUEST = "The contact cannot be deleted! "
+ + "Because it has been linked to one or more jobs!";
+ public static final String MESSAGE_INVALID_TEMPLATE_DISPLAYED_INDEX = "The template index provided is invalid";
+ public static final String MESSAGE_INVALID_JOB_NO_EMAIL = "The job specified does not contain an email. Do "
+ + "contact him/her through other means!";
+ public static final String MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX = "The product index provided is invalid";
+ public static final String MESSAGE_INVALID_JOB_DISPLAYED_INDEX = "The job index provided is invalid";
+ public static final String MESSAGE_INVALID_JOB_COMPLETE_REQUEST = "The job has already been completed!";
+ public static final String MESSAGE_INVALID_JOB_EXPECTED_COMPLETION_DATE = "Job's expected completion date cannot "
+ + "be before its received date. Please provide a valid expected completion date "
+ + "or change the job's received date.";
+ public static final String MESSAGE_INVALID_JOB_COMPLETION_DATE = "Job's completion date cannot be before "
+ + "its received date. Please provide a valid completion date or change the job's received date.";
+ public static final String MESSAGE_INVALID_JOB_UNDO_COMPLETE_REQUEST = "The job has not been completed yet!";
+ public static final String MESSAGE_CONTACTS_LISTED_OVERVIEW = "%1$d contacts listed!";
+ public static final String MESSAGE_JOBS_LISTED_OVERVIEW = "%1$d jobs listed!";
+ public static final String MESSAGE_PRODUCTS_LISTED_OVERVIEW = "%1$d products listed!";
+ public static final String MESSAGE_TEMPLATES_LISTED_OVERVIEW = "%1$d templates listed!";
+ public static final String MESSAGE_REMOVE_LINKED_PRODUCT = "The product is linked to one or more job.";
+}
diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/mycrm/commons/core/Version.java
similarity index 98%
rename from src/main/java/seedu/address/commons/core/Version.java
rename to src/main/java/seedu/mycrm/commons/core/Version.java
index 12142ec1e32..f47d36c4e71 100644
--- a/src/main/java/seedu/address/commons/core/Version.java
+++ b/src/main/java/seedu/mycrm/commons/core/Version.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package seedu.mycrm.commons.core;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/mycrm/commons/core/index/Index.java
similarity index 97%
rename from src/main/java/seedu/address/commons/core/index/Index.java
rename to src/main/java/seedu/mycrm/commons/core/index/Index.java
index 19536439c09..75ea0bbe930 100644
--- a/src/main/java/seedu/address/commons/core/index/Index.java
+++ b/src/main/java/seedu/mycrm/commons/core/index/Index.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core.index;
+package seedu.mycrm.commons.core.index;
/**
* Represents a zero-based or one-based index.
diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/seedu/mycrm/commons/exceptions/DataConversionException.java
similarity index 84%
rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java
rename to src/main/java/seedu/mycrm/commons/exceptions/DataConversionException.java
index 1f689bd8e3f..e26b39d4fc3 100644
--- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java
+++ b/src/main/java/seedu/mycrm/commons/exceptions/DataConversionException.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.exceptions;
+package seedu.mycrm.commons.exceptions;
/**
* Represents an error during conversion of data from one format to another
diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/mycrm/commons/exceptions/IllegalValueException.java
similarity index 93%
rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
rename to src/main/java/seedu/mycrm/commons/exceptions/IllegalValueException.java
index 19124db485c..1613c1de7d3 100644
--- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
+++ b/src/main/java/seedu/mycrm/commons/exceptions/IllegalValueException.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.exceptions;
+package seedu.mycrm.commons.exceptions;
/**
* Signals that some given data does not fulfill some constraints.
diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/mycrm/commons/util/AppUtil.java
similarity index 94%
rename from src/main/java/seedu/address/commons/util/AppUtil.java
rename to src/main/java/seedu/mycrm/commons/util/AppUtil.java
index 87aa89c0326..0781286a6f3 100644
--- a/src/main/java/seedu/address/commons/util/AppUtil.java
+++ b/src/main/java/seedu/mycrm/commons/util/AppUtil.java
@@ -1,9 +1,9 @@
-package seedu.address.commons.util;
+package seedu.mycrm.commons.util;
import static java.util.Objects.requireNonNull;
import javafx.scene.image.Image;
-import seedu.address.MainApp;
+import seedu.mycrm.MainApp;
/**
* A container for App specific utility functions
diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/mycrm/commons/util/CollectionUtil.java
similarity index 96%
rename from src/main/java/seedu/address/commons/util/CollectionUtil.java
rename to src/main/java/seedu/mycrm/commons/util/CollectionUtil.java
index eafe4dfd681..219f2566836 100644
--- a/src/main/java/seedu/address/commons/util/CollectionUtil.java
+++ b/src/main/java/seedu/mycrm/commons/util/CollectionUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package seedu.mycrm.commons.util;
import static java.util.Objects.requireNonNull;
diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/seedu/mycrm/commons/util/ConfigUtil.java
similarity index 77%
rename from src/main/java/seedu/address/commons/util/ConfigUtil.java
rename to src/main/java/seedu/mycrm/commons/util/ConfigUtil.java
index f7f8a2bd44c..1e1231f2d22 100644
--- a/src/main/java/seedu/address/commons/util/ConfigUtil.java
+++ b/src/main/java/seedu/mycrm/commons/util/ConfigUtil.java
@@ -1,11 +1,11 @@
-package seedu.address.commons.util;
+package seedu.mycrm.commons.util;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.exceptions.DataConversionException;
+import seedu.mycrm.commons.core.Config;
+import seedu.mycrm.commons.exceptions.DataConversionException;
/**
* A class for accessing the Config File.
diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/mycrm/commons/util/FileUtil.java
similarity index 98%
rename from src/main/java/seedu/address/commons/util/FileUtil.java
rename to src/main/java/seedu/mycrm/commons/util/FileUtil.java
index b1e2767cdd9..6a62addb3cd 100644
--- a/src/main/java/seedu/address/commons/util/FileUtil.java
+++ b/src/main/java/seedu/mycrm/commons/util/FileUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package seedu.mycrm.commons.util;
import java.io.IOException;
import java.nio.file.Files;
diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/mycrm/commons/util/JsonUtil.java
similarity index 97%
rename from src/main/java/seedu/address/commons/util/JsonUtil.java
rename to src/main/java/seedu/mycrm/commons/util/JsonUtil.java
index 8ef609f055d..36166693aeb 100644
--- a/src/main/java/seedu/address/commons/util/JsonUtil.java
+++ b/src/main/java/seedu/mycrm/commons/util/JsonUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package seedu.mycrm.commons.util;
import static java.util.Objects.requireNonNull;
@@ -20,8 +20,8 @@
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataConversionException;
+import seedu.mycrm.commons.core.LogsCenter;
+import seedu.mycrm.commons.exceptions.DataConversionException;
/**
* Converts a Java object instance to JSON and vice versa
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/mycrm/commons/util/StringUtil.java
similarity index 95%
rename from src/main/java/seedu/address/commons/util/StringUtil.java
rename to src/main/java/seedu/mycrm/commons/util/StringUtil.java
index 61cc8c9a1cb..691fd79f6f1 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/mycrm/commons/util/StringUtil.java
@@ -1,7 +1,7 @@
-package seedu.address.commons.util;
+package seedu.mycrm.commons.util;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
+import static seedu.mycrm.commons.util.AppUtil.checkArgument;
import java.io.PrintWriter;
import java.io.StringWriter;
diff --git a/src/main/java/seedu/mycrm/logic/Logic.java b/src/main/java/seedu/mycrm/logic/Logic.java
new file mode 100644
index 00000000000..550637e1fc0
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/Logic.java
@@ -0,0 +1,88 @@
+package seedu.mycrm.logic;
+
+import java.nio.file.Path;
+import java.time.LocalDate;
+
+import javafx.collections.ObservableList;
+import seedu.mycrm.commons.core.GuiSettings;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.ReadOnlyMyCrm;
+import seedu.mycrm.model.contact.Contact;
+import seedu.mycrm.model.history.History;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.mail.Mail;
+import seedu.mycrm.model.mail.Template;
+import seedu.mycrm.model.product.Product;
+
+/**
+ * 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.
+ * @throws CommandException If an error occurs during command execution.
+ * @throws ParseException If an error occurs during parsing.
+ */
+ CommandResult execute(String commandText) throws CommandException, ParseException;
+
+ /**
+ * Returns the MyCrm.
+ *
+ * @see seedu.mycrm.model.Model#getMyCrm()
+ */
+ ReadOnlyMyCrm getMyCrm();
+
+ /** Returns an unmodifiable view of the filtered list of contacts */
+ ObservableList getFilteredContactList();
+
+ /** Returns an unmodifiable view of the filtered list of templates */
+ ObservableList getFilteredTemplateList();
+
+ /** Returns an unmodifiable view of the filtered list of mails */
+ ObservableList getFilteredMailList();
+
+ /** Returns an unmodifiable view of the filtered list of products */
+ ObservableList getFilteredProductList();
+
+ /** Returns an unmodifiable view of the filtered list of top three products */
+ ObservableList getFilteredTopThreeProductList();
+
+ /** Returns an unmodifiable view of the filtered list of jobs */
+ ObservableList getFilteredJobList();
+
+ /** Returns an unmodifiable view of the filtered list of incomplete jobs */
+ ObservableList getFilteredIncompleteJobList();
+
+ /** Returns an unmodifiable view of the filtered list of monthly completed jobs */
+ ObservableList getFilteredMonthlyCompletedJobList();
+
+ /** Returns an unmodifiable view of the filtered list of history commands */
+ ObservableList getFilteredHistoryList();
+
+ double getRevenue(LocalDate date);
+
+ /**
+ * Returns the user prefs' myCrm file path.
+ */
+ Path getMyCrmFilePath();
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Set the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+
+ /**
+ * Tracing UserInput from CommandBox.
+ */
+ void traceUserInput(History history);
+
+}
diff --git a/src/main/java/seedu/mycrm/logic/LogicManager.java b/src/main/java/seedu/mycrm/logic/LogicManager.java
new file mode 100644
index 00000000000..0acc5c23c61
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/LogicManager.java
@@ -0,0 +1,155 @@
+package seedu.mycrm.logic;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.time.LocalDate;
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import seedu.mycrm.commons.core.GuiSettings;
+import seedu.mycrm.commons.core.LogsCenter;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.logic.parser.MyCrmParser;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.ReadOnlyMyCrm;
+import seedu.mycrm.model.contact.Contact;
+import seedu.mycrm.model.history.History;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.mail.Mail;
+import seedu.mycrm.model.mail.Template;
+import seedu.mycrm.model.product.Product;
+import seedu.mycrm.storage.Storage;
+
+/**
+ * The main LogicManager of the app.
+ */
+public class LogicManager implements Logic {
+ public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: ";
+ private final Logger logger = LogsCenter.getLogger(LogicManager.class);
+
+ private final Model model;
+ private final Storage storage;
+ private final MyCrmParser myCrmParser;
+ private final StateManager stateManager;
+
+ /**
+ * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}.
+ */
+ public LogicManager(Model model, Storage storage) {
+ this.model = model;
+ this.storage = storage;
+ myCrmParser = new MyCrmParser();
+ stateManager = new StateManager(model);
+ }
+
+ @Override
+ public CommandResult execute(String commandText) throws CommandException, ParseException {
+ logger.info("----------------[USER COMMAND][" + commandText + "]");
+
+ CommandResult commandResult;
+ Command command;
+
+ try {
+ String commandWord = MyCrmParser.parseCommandWord(commandText);
+ if (!stateManager.isCommandAllowedForState(commandWord)) {
+ throw new CommandException(stateManager.getCommandNotAllowedMessage(commandWord));
+ }
+ command = myCrmParser.parseCommand(commandText);
+
+ } catch (ParseException e) {
+ throw new ParseException(stateManager.getErrorMessage() + e.getMessage());
+ }
+
+ try {
+ commandResult = command.execute(model, stateManager);
+ } catch (CommandException e) {
+ throw new CommandException(stateManager.getErrorMessage() + e.getMessage());
+ }
+
+ try {
+ storage.saveMyCrm(model.getMyCrm());
+ } catch (IOException ioe) {
+ throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
+ }
+
+ return commandResult;
+ }
+
+ @Override
+ public ReadOnlyMyCrm getMyCrm() {
+ return model.getMyCrm();
+ }
+
+ @Override
+ public ObservableList getFilteredContactList() {
+ return model.getFilteredContactList();
+ }
+
+ @Override
+ public ObservableList getFilteredTemplateList() {
+ return model.getFilteredTemplateList();
+ }
+
+ @Override
+ public ObservableList getFilteredMailList() {
+ return model.getFilteredMailList();
+ }
+
+ @Override
+ public ObservableList getFilteredProductList() {
+ return model.getFilteredProductList();
+ }
+
+ @Override
+ public ObservableList getFilteredTopThreeProductList() {
+ return model.getFilteredTopThreeProductList();
+ }
+
+ @Override
+ public ObservableList getFilteredJobList() {
+ return model.getFilteredJobList();
+ }
+
+ @Override
+ public ObservableList getFilteredIncompleteJobList() {
+ return model.getFilteredIncompleteJobList();
+ }
+
+ @Override
+ public ObservableList getFilteredMonthlyCompletedJobList() {
+ return model.getFilteredMonthlyCompletedJobList();
+ }
+
+ @Override
+ public double getRevenue(LocalDate date) {
+ return model.getRevenue(date);
+ }
+
+ @Override
+ public ObservableList getFilteredHistoryList() {
+ return model.getFilteredHistoryList();
+ };
+
+ @Override
+ public Path getMyCrmFilePath() {
+ return model.getMyCrmFilePath();
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return model.getGuiSettings();
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ model.setGuiSettings(guiSettings);
+ }
+
+ @Override
+ public void traceUserInput(History history) {
+ model.addHistory(history);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/StateManager.java b/src/main/java/seedu/mycrm/logic/StateManager.java
new file mode 100644
index 00000000000..0d1b1cee122
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/StateManager.java
@@ -0,0 +1,431 @@
+package seedu.mycrm.logic;
+
+import static seedu.mycrm.logic.commands.jobs.AddJobCommand.MESSAGE_DUPLICATE_JOB;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.AbortCommand;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.SelectCommand;
+import seedu.mycrm.logic.commands.contacts.AddContactCommand;
+import seedu.mycrm.logic.commands.contacts.FindContactCommand;
+import seedu.mycrm.logic.commands.contacts.ListContactCommand;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.logic.commands.jobs.AddJobCommand;
+import seedu.mycrm.logic.commands.jobs.EditJobCommand;
+import seedu.mycrm.logic.commands.products.AddProductCommand;
+import seedu.mycrm.logic.commands.products.FindProductCommand;
+import seedu.mycrm.logic.commands.products.ListProductCommand;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.contact.Contact;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.product.Product;
+
+/**
+ * Responsible for maintaining state for previous commands and
+ * modifying behaviour based on current state.
+ */
+public class StateManager {
+
+ private static final String NEW_JOB_CONTACT_MESSAGE = "Currently adding a new job. "
+ + "Please provide a contact for the Job: %s.\n";
+
+ private static final String NEW_JOB_PRODUCT_MESSAGE = "Currently adding a new job. "
+ + "Please provide a product for the Job: %s.\n";
+
+ private static final String EDIT_JOB_CONTACT_MESSAGE = "Currently editing the contact for job %s.\n";
+
+ private static final String EDIT_JOB_PRODUCT_MESSAGE = "Currently editing the product for job %s.\n";
+
+ private static final String PROVIDE_CONTACT_INSTR = "You can either assign a new contact to the job by "
+ + "issuing a addContact command or select an existing one "
+ + "from the contact list by issuing a select command.\n";
+
+ private static final String CONTACT_PROVIDED_MESSAGE = "Following contact: %s will be assigned to the job.\n";
+
+ private static final String PROVIDE_PRODUCT_INSTR = "You can either assign a a new product to the job by "
+ + "issuing a addProduct command or select an existing one "
+ + "from the contact list by issuing a select command.\n";
+
+ private static final String PRODUCT_PROVIDED_MESSAGE = "Following product: %s will be assigned to the job.\n";
+
+ private static final String ERROR_MESSAGE = "There was an error in the command issued. "
+ + "Please try to issue the command again after correcting based on the info below.\n";
+
+ private static final String COMMAND_NOT_ALLOWED_MESSAGE =
+ "Command %s is now allowed right now\n" + "You can issue the abort command to stop the current operation\n";
+
+ private static final String ADD_JOB_ABORTED = "New job %s will not added to MyCRM\n"
+ + "However, any contact or product added during the operation using "
+ + "addJob or addProduct have been added to MyCRM\n";
+
+ private static final String EDIT_JOB_ABORTED = "No attributes of Job: %s will be edited\n"
+ + "However, any contact or product added during the operation using "
+ + "addJob or addProduct have been added to MyCRM\n";
+
+
+ private enum State {
+ NEW_JOB_CONTACT(NEW_JOB_CONTACT_MESSAGE, PROVIDE_CONTACT_INSTR,
+ CONTACT_PROVIDED_MESSAGE, CommandType.CONTACTS),
+
+ NEW_JOB_PRODUCT(NEW_JOB_PRODUCT_MESSAGE, PROVIDE_PRODUCT_INSTR,
+ PRODUCT_PROVIDED_MESSAGE, CommandType.PRODUCTS),
+
+ EDIT_JOB_CONTACT(EDIT_JOB_CONTACT_MESSAGE, PROVIDE_CONTACT_INSTR,
+ CONTACT_PROVIDED_MESSAGE, CommandType.CONTACTS),
+
+ EDIT_JOB_PRODUCT(EDIT_JOB_PRODUCT_MESSAGE, PROVIDE_PRODUCT_INSTR,
+ PRODUCT_PROVIDED_MESSAGE, CommandType.PRODUCTS);
+
+ private String userMessage;
+ private String instructions;
+ private String successMessage;
+ private CommandType commandType;
+
+ private State(String userMessage, String instructions, String successMessage, CommandType commandType) {
+ this.userMessage = userMessage;
+ this.instructions = instructions;
+ this.successMessage = successMessage;
+ this.commandType = commandType;
+ }
+
+ public String getUserMessage() {
+ return userMessage;
+ }
+
+ public String getInstructions() {
+ return instructions;
+ }
+
+ public String getSuccessMessage() {
+ return successMessage;
+ }
+
+ public CommandType getCommandType() {
+ return commandType;
+ }
+ }
+
+ private static Map> allowedCommandsForState;
+ private Job job;
+ private Job jobToEdit;
+ private Model model;
+ private State currentState;
+ private Queue stateQueue;
+
+ /**
+ * Constructs a StateManager object.
+ */
+ public StateManager(Model model) {
+ this.model = model;
+ stateQueue = new LinkedList<>();
+ initialiseAllowedCommandsForState();
+ }
+
+ private static void initialiseAllowedCommandsForState() {
+ allowedCommandsForState = new HashMap<>();
+
+ allowedCommandsForState.put(State.NEW_JOB_CONTACT,
+ new ArrayList<>(Arrays.asList(AddContactCommand.COMMAND_WORD,
+ FindContactCommand.COMMAND_WORD, ListContactCommand.COMMAND_WORD,
+ SelectCommand.COMMAND_WORD, AbortCommand.COMMAND_WORD)));
+
+ allowedCommandsForState.put(State.NEW_JOB_PRODUCT,
+ new ArrayList<>(Arrays.asList(AddProductCommand.COMMAND_WORD,
+ FindProductCommand.COMMAND_WORD, ListProductCommand.COMMAND_WORD,
+ SelectCommand.COMMAND_WORD, AbortCommand.COMMAND_WORD)));
+
+ allowedCommandsForState.put(State.EDIT_JOB_CONTACT,
+ new ArrayList<>(Arrays.asList(AddContactCommand.COMMAND_WORD,
+ FindContactCommand.COMMAND_WORD, ListContactCommand.COMMAND_WORD,
+ SelectCommand.COMMAND_WORD, AbortCommand.COMMAND_WORD)));
+
+ allowedCommandsForState.put(State.EDIT_JOB_PRODUCT,
+ new ArrayList<>(Arrays.asList(AddProductCommand.COMMAND_WORD,
+ FindProductCommand.COMMAND_WORD, ListProductCommand.COMMAND_WORD,
+ SelectCommand.COMMAND_WORD, AbortCommand.COMMAND_WORD)));
+ }
+
+ /**
+ * Handles the behaviour of the addJob command based on whether a
+ * product and client have been assigned.
+ *
+ * @param job Job to be added to MyCRM.
+ * @return Result of the execution of the addJob command.
+ */
+ public CommandResult handleAddJob(Job job) throws CommandException {
+ this.job = job;
+
+
+ if (job.hasProduct() && job.hasClient()) {
+ model.addJob(job);
+ return new CommandResult(String.format(AddJobCommand.MESSAGE_SUCCESS, job), CommandType.JOBS);
+ }
+
+ if (!job.hasClient()) {
+ stateQueue.add(State.NEW_JOB_CONTACT);
+ }
+
+ if (!job.hasProduct()) {
+ stateQueue.add(State.NEW_JOB_PRODUCT);
+ }
+
+ try {
+ return constructCommandResult(null, "");
+ } catch (CommandException e) {
+ throw new CommandException(getErrorMessage() + e.getMessage());
+ }
+ }
+
+
+ /**
+ * Handles the behaviour of the editJob command based on whether a product or client
+ * of a job need to be edited.
+ *
+ * @param jobToEdit Existing job in MyCRM that needs to be edited.
+ * @param editedJob Edited Job which will replace the existing job.
+ * @param shouldEditContact Whether the job's contact is being edited.
+ * @param shouldEditProduct Whether the job's product is being edited.
+ * @return
+ */
+ public CommandResult handleEditJob(Job jobToEdit, Job editedJob,
+ boolean shouldEditContact, boolean shouldEditProduct, CommandResult commandResult) throws CommandException {
+
+ this.job = editedJob;
+ this.jobToEdit = jobToEdit;
+
+ if (!shouldEditContact && !shouldEditProduct) {
+ model.setJob(jobToEdit, job);
+ return new CommandResult(String.format(EditJobCommand.MESSAGE_EDIT_JOB_SUCCESS, job),
+ CommandType.JOBS);
+ }
+
+ if (shouldEditContact) {
+ stateQueue.add(State.EDIT_JOB_CONTACT);
+ }
+
+ if (shouldEditProduct) {
+ stateQueue.add(State.EDIT_JOB_PRODUCT);
+ }
+
+ try {
+ return constructCommandResult(null, "");
+ } catch (CommandException e) {
+ throw new CommandException(getErrorMessage() + e.getMessage());
+ }
+ }
+
+ /**
+ * Handles the behaviour of the addProduct command if it is used in the context
+ * of a addJob or editJob command.
+ *
+ * @param product Product that was added.
+ * @param commandResult Result of the original addProduct command.
+ * @return Modified result of the addProduct command based on current state.
+ */
+ public CommandResult handleProduct(Product product, CommandResult commandResult) throws CommandException {
+ if (currentState == State.NEW_JOB_PRODUCT || currentState == State.EDIT_JOB_PRODUCT) {
+ job.setProduct(product);
+ try {
+ return constructCommandResult(commandResult, String.format(currentState.getSuccessMessage(), product));
+ } catch (CommandException e) {
+ throw new CommandException(getErrorMessage() + e.getMessage());
+ }
+
+
+ } else {
+ return commandResult;
+ }
+ }
+
+ /**
+ * Handles the behaviour of the addContact command if it is used in the context
+ * of a addJob or editJob command.
+ *
+ * @param contact Contact that was added.
+ * @param commandResult Result of the original addContact command.
+ * @return Modified result of the addContact command based on current state.
+ */
+ public CommandResult handleContact(Contact contact, CommandResult commandResult) throws CommandException {
+ if (currentState == State.NEW_JOB_CONTACT || currentState == State.EDIT_JOB_CONTACT) {
+ job.setClient(contact);
+ try {
+ return constructCommandResult(commandResult, String.format(currentState.getSuccessMessage(), contact));
+ } catch (CommandException e) {
+ throw new CommandException(getErrorMessage() + e.getMessage());
+ }
+ } else {
+ return commandResult;
+ }
+ }
+
+ /**
+ * Handles the behaviour of the select command based on the context of
+ * selecting a product or contact.
+ *
+ * @param targetIndex Index of the displayed product or contact.
+ * @return Result of the execution of the select command.
+ * @throws CommandException If the index is invalid.
+ */
+ public CommandResult handleIndex(Index targetIndex) throws CommandException {
+ if (currentState == State.NEW_JOB_CONTACT || currentState == State.EDIT_JOB_CONTACT) {
+ List lastShownContactList = model.getFilteredContactList();
+ if (targetIndex.getZeroBased() >= lastShownContactList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+
+ Contact client = lastShownContactList.get(targetIndex.getZeroBased());
+ job.setClient(client);
+ return constructCommandResult(null, String.format(currentState.getSuccessMessage(), client));
+
+ } else if (currentState == State.NEW_JOB_PRODUCT || currentState == State.EDIT_JOB_PRODUCT) {
+ List lastShownProductList = model.getFilteredProductList();
+ if (targetIndex.getZeroBased() >= lastShownProductList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX);
+ }
+
+ Product product = lastShownProductList.get(targetIndex.getZeroBased());
+ job.setProduct(product);
+ return constructCommandResult(null, String.format(currentState.getSuccessMessage(), product));
+ } else {
+ throw new CommandException("Invalid usage of Select Command\n" + SelectCommand.MESSAGE_USAGE);
+ }
+ }
+
+ /**
+ * Handles the behaviour of list or find commands for product command.
+ * Modifies the feedBackToUser string of the command result.
+ *
+ * @param commandResult Original result of the execution of a list or find command.
+ * @return CommandResult with modified feedbackToUser string.
+ */
+ public CommandResult handleList(CommandResult commandResult) {
+ if (currentState == null) {
+ return commandResult;
+ }
+ String userFeedback = String.format(currentState.getUserMessage(), job);
+ userFeedback += commandResult.getFeedbackToUser();
+ return new CommandResult(userFeedback, commandResult.getCommandType());
+ }
+
+ /**
+ * Handles the aborting of the addJob or editJob operation.
+ *
+ * @return Result of the execution of the abort command.
+ * @throws CommandException If the usage of abort is done outside the context of a
+ * addJob or editJob command.
+ */
+ public CommandResult handleAbort() throws CommandException {
+ CommandResult result;
+ if (isJobBeingEdited()) {
+ result = new CommandResult(String.format(EDIT_JOB_ABORTED, jobToEdit), CommandType.JOBS);
+
+ } else if (isJobBeingAdded()) {
+ result = new CommandResult(String.format(ADD_JOB_ABORTED, job), CommandType.JOBS);
+ } else {
+ throw new CommandException("Invalid usage of Abort Command\n" + AbortCommand.MESSAGE_USAGE);
+ }
+
+ clearState();
+ return result;
+ }
+
+
+ /**
+ * Checks if the command issued is allowed based on the current state.
+ *
+ * @param commandWord Command Word of the command to be executed.
+ * @return Whether the command is allowed based on the current state.
+ */
+ public boolean isCommandAllowedForState(String commandWord) {
+ if (currentState == null) {
+ return true;
+ }
+ List allowedCommands = allowedCommandsForState.get(currentState);
+ return allowedCommands.contains(commandWord);
+ }
+
+ /**
+ * Returns an error message based on current state.
+ */
+ public String getErrorMessage() {
+ String userFeedback = "";
+ if (currentState != null) {
+ userFeedback += String.format(currentState.getUserMessage(), job);
+ userFeedback += ERROR_MESSAGE;
+ }
+ return userFeedback;
+ }
+
+ /**
+ * Returns a command not allowed message based on current state.
+ */
+ public String getCommandNotAllowedMessage(String commandWord) {
+ if (currentState == null) {
+ return "";
+ }
+
+ String userFeedback = String.format(COMMAND_NOT_ALLOWED_MESSAGE, commandWord);
+ userFeedback += String.format(currentState.getUserMessage(), job);
+ return userFeedback;
+ }
+
+ private CommandResult constructCommandResult(CommandResult commandResult, String message) throws CommandException {
+ State nextState = stateQueue.poll();
+
+ String userFeedback;
+
+ if (commandResult == null) {
+ userFeedback = message;
+ } else {
+ userFeedback = commandResult.getFeedbackToUser() + message;
+ }
+
+ if (isJobBeingAdded() && nextState == null) {
+ if (model.hasJob(job)) {
+ throw new CommandException(MESSAGE_DUPLICATE_JOB);
+ }
+ model.addJob(job);
+ userFeedback += String.format(AddJobCommand.MESSAGE_SUCCESS, job);
+ clearState();
+ return new CommandResult(userFeedback, CommandType.JOBS);
+ } else if (isJobBeingEdited() && nextState == null) {
+ if (model.hasJob(job)) {
+ throw new CommandException(MESSAGE_DUPLICATE_JOB);
+ }
+ model.setJob(jobToEdit, job);
+ userFeedback += String.format(EditJobCommand.MESSAGE_EDIT_JOB_SUCCESS, job);
+ clearState();
+ return new CommandResult(userFeedback, CommandType.JOBS);
+ } else {
+ userFeedback += String.format(nextState.getUserMessage(), job);
+ userFeedback += nextState.getInstructions();
+ currentState = nextState;
+ return new CommandResult(userFeedback, nextState.getCommandType());
+ }
+ }
+
+ private void clearState() {
+ job = null;
+ jobToEdit = null;
+ stateQueue.clear();
+ currentState = null;
+ }
+
+ private boolean isJobBeingEdited() {
+ return (currentState == State.EDIT_JOB_CONTACT || currentState == State.EDIT_JOB_PRODUCT);
+ }
+
+ private boolean isJobBeingAdded() {
+ return (currentState == State.NEW_JOB_CONTACT || currentState == State.NEW_JOB_PRODUCT);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/AbortCommand.java b/src/main/java/seedu/mycrm/logic/commands/AbortCommand.java
new file mode 100644
index 00000000000..64756f20991
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/AbortCommand.java
@@ -0,0 +1,39 @@
+package seedu.mycrm.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+
+public class AbortCommand extends Command {
+ public static final String COMMAND_WORD = "abort";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Only used in conjunction with addJob or editJob when assigning product or contact to job\n"
+ + "Will abort the current operation. For addJob the new job will not be added\n"
+ + "For editJob the job will remain unedited\n";
+
+ private static final CommandType COMMAND_TYPE = CommandType.JOBS;
+
+ public AbortCommand() {
+
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ return stateManager.handleAbort();
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AbortCommand); // instanceof handles nulls
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/ClearCommand.java b/src/main/java/seedu/mycrm/logic/commands/ClearCommand.java
new file mode 100644
index 00000000000..aaa5964c764
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/ClearCommand.java
@@ -0,0 +1,30 @@
+package seedu.mycrm.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.MyCrm;
+
+/**
+ * Clears the myCrm.
+ */
+public class ClearCommand extends Command {
+
+ public static final String COMMAND_WORD = "clear";
+ public static final String MESSAGE_SUCCESS = "myCrm has been cleared!";
+
+ private static final CommandType COMMAND_TYPE = CommandType.COMMON;
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ requireNonNull(model);
+ model.setMyCrm(new MyCrm());
+ return new CommandResult(MESSAGE_SUCCESS, COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/mycrm/logic/commands/Command.java
similarity index 55%
rename from src/main/java/seedu/address/logic/commands/Command.java
rename to src/main/java/seedu/mycrm/logic/commands/Command.java
index 64f18992160..9159519c2f6 100644
--- a/src/main/java/seedu/address/logic/commands/Command.java
+++ b/src/main/java/seedu/mycrm/logic/commands/Command.java
@@ -1,7 +1,8 @@
-package seedu.address.logic.commands;
+package seedu.mycrm.logic.commands;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
/**
* Represents a command with hidden internal logic and the ability to be executed.
@@ -12,9 +13,11 @@ public abstract class Command {
* Executes the command and returns the result message.
*
* @param model {@code Model} which the command should operate on.
+ * @param stateManager
* @return feedback message of the operation result for display
* @throws CommandException If an error occurs during command execution.
*/
- public abstract CommandResult execute(Model model) throws CommandException;
+ public abstract CommandResult execute(Model model, StateManager stateManager) throws CommandException;
+ public abstract CommandType getType();
}
diff --git a/src/main/java/seedu/mycrm/logic/commands/CommandResult.java b/src/main/java/seedu/mycrm/logic/commands/CommandResult.java
new file mode 100644
index 00000000000..3fc4d82b9f1
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/CommandResult.java
@@ -0,0 +1,104 @@
+package seedu.mycrm.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Objects;
+
+/**
+ * Represents the result of a command execution.
+ */
+public class CommandResult {
+
+ private final String feedbackToUser;
+
+ private final CommandType commandType;
+
+ private final String themeName;
+
+ private final String commandFlag;
+
+ /**
+ * Constructs a {@code CommandResult} with the specified {@code feedbackToUser} and {@code commandType},
+ * and other fields set to their default value.
+ */
+ public CommandResult(String feedbackToUser, CommandType commandType) {
+ assert commandType != CommandType.THEME;
+
+ this.feedbackToUser = requireNonNull(feedbackToUser);
+ this.commandType = commandType;
+ this.themeName = null; // theme name is set to null by default
+ this.commandFlag = null; // command flag is set to null by default
+ }
+
+ /**
+ * Constructs a {@code CommandResult} with the specified {@code feedbackToUser},
+ * and other fields set to their default value.
+ */
+ public CommandResult(String feedbackToUser) {
+ this(feedbackToUser, CommandType.COMMON);
+ }
+
+ /**
+ * Constructs a {@code CommandResult} of either theme command or print report command with the specified fields.
+ */
+ public CommandResult(String feedbackToUser, CommandType commandType, String message) {
+ assert commandType == CommandType.THEME || commandType == CommandType.REPORT;
+ requireNonNull(feedbackToUser, message);
+
+ this.feedbackToUser = feedbackToUser;
+ this.commandType = commandType;
+
+ if (commandType == CommandType.THEME) {
+ this.themeName = message;
+ this.commandFlag = null;
+ } else {
+ assert commandType == CommandType.REPORT;
+ this.themeName = null;
+ this.commandFlag = message;
+ }
+ }
+
+ public String getFeedbackToUser() {
+ return feedbackToUser;
+ }
+
+ public CommandType getCommandType() {
+ return commandType;
+ }
+
+ /**
+ * Returns the name of desired theme.
+ */
+ public String getThemeName() {
+ return this.themeName;
+ }
+
+ /**
+ * Returns flag of command if the instance is created by {@code PrintReportCommand}.
+ */
+ public String getCommandFlag() {
+ return this.commandFlag;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof CommandResult)) {
+ return false;
+ }
+
+ CommandResult otherCommandResult = (CommandResult) other;
+ return feedbackToUser.equals(otherCommandResult.feedbackToUser)
+ && commandType == otherCommandResult.commandType
+ && Objects.equals(this.getThemeName(), otherCommandResult.themeName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(feedbackToUser, commandType, themeName);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/CommandType.java b/src/main/java/seedu/mycrm/logic/commands/CommandType.java
new file mode 100644
index 00000000000..9d71897c9d3
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/CommandType.java
@@ -0,0 +1,6 @@
+package seedu.mycrm.logic.commands;
+
+/** Represents the type of a command. */
+public enum CommandType {
+ CONTACTS, JOBS, TEMPLATE, MAIL, PRODUCTS, HISTORY, EXIT, HELP, REPORT, EXPORT, THEME, COMMON,
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/ExitCommand.java b/src/main/java/seedu/mycrm/logic/commands/ExitCommand.java
new file mode 100644
index 00000000000..69bae9f1818
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/ExitCommand.java
@@ -0,0 +1,26 @@
+package seedu.mycrm.logic.commands;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.model.Model;
+
+/**
+ * Terminates the program.
+ */
+public class ExitCommand extends Command {
+
+ public static final String COMMAND_WORD = "exit";
+
+ public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting myCrm as requested ...";
+
+ private static final CommandType COMMAND_TYPE = CommandType.EXIT;
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/ExportReportCommand.java b/src/main/java/seedu/mycrm/logic/commands/ExportReportCommand.java
new file mode 100644
index 00000000000..8037bfd7a4d
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/ExportReportCommand.java
@@ -0,0 +1,33 @@
+package seedu.mycrm.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+
+public class ExportReportCommand extends Command {
+ public static final String COMMAND_WORD = "exportReport";
+
+ public static final String SHOWING_EXPORT_MESSAGE = "Printing report.";
+
+ public static final String MESSAGE_EMPTY_JOB_LIST = "There is no job this month.";
+
+ private static final CommandType COMMAND_TYPE = CommandType.EXPORT;
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+
+ if (model.getFilteredAllJobList().isEmpty()) {
+ throw new CommandException(MESSAGE_EMPTY_JOB_LIST);
+ }
+
+ return new CommandResult(SHOWING_EXPORT_MESSAGE, COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/mycrm/logic/commands/HelpCommand.java
similarity index 50%
rename from src/main/java/seedu/address/logic/commands/HelpCommand.java
rename to src/main/java/seedu/mycrm/logic/commands/HelpCommand.java
index bf824f91bd0..7ac0f452471 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/seedu/mycrm/logic/commands/HelpCommand.java
@@ -1,6 +1,7 @@
-package seedu.address.logic.commands;
+package seedu.mycrm.logic.commands;
-import seedu.address.model.Model;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.model.Model;
/**
* Format full help instructions for every command for display.
@@ -14,8 +15,15 @@ public class HelpCommand extends Command {
public static final String SHOWING_HELP_MESSAGE = "Opened help window.";
+ private static final CommandType COMMAND_TYPE = CommandType.HELP;
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ return new CommandResult(SHOWING_HELP_MESSAGE, COMMAND_TYPE);
+ }
+
@Override
- public CommandResult execute(Model model) {
- return new CommandResult(SHOWING_HELP_MESSAGE, true, false);
+ public CommandType getType() {
+ return COMMAND_TYPE;
}
}
diff --git a/src/main/java/seedu/mycrm/logic/commands/PrintReportCommand.java b/src/main/java/seedu/mycrm/logic/commands/PrintReportCommand.java
new file mode 100644
index 00000000000..26c81f738e1
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/PrintReportCommand.java
@@ -0,0 +1,58 @@
+package seedu.mycrm.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+
+/**
+ * Prints out monthly job records and statistics.
+ */
+public class PrintReportCommand extends Command {
+ public static final String SHOW_COMPLETED_FLAG = "-c";
+ public static final String SHOW_IN_PROGRESS_FLAG = "-i";
+ public static final String SHOW_PRODUCT_FLAG = "-p";
+
+ public static final String COMMAND_WORD = "printReport";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Print monthly job report.\n"
+ + "Example: " + COMMAND_WORD;
+
+ public static final String SHOWING_REPORT_MESSAGE = "Opened report window.";
+
+ public static final String MESSAGE_EMPTY_JOB_LIST = "There is no job this month.";
+
+ private static final CommandType COMMAND_TYPE = CommandType.REPORT;
+
+ private final String flag;
+
+ /**
+ * Creates an ListJobCommand to list jobs matching the {@code listPredicate}.
+ */
+ public PrintReportCommand(String flag) {
+ requireNonNull(flag);
+ this.flag = flag;
+ }
+
+ public PrintReportCommand() {
+ this.flag = SHOW_COMPLETED_FLAG;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ requireNonNull(flag);
+
+ if (model.getFilteredAllJobList().isEmpty()) {
+ throw new CommandException(MESSAGE_EMPTY_JOB_LIST);
+ }
+
+ return new CommandResult(SHOWING_REPORT_MESSAGE, COMMAND_TYPE, flag);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/SelectCommand.java b/src/main/java/seedu/mycrm/logic/commands/SelectCommand.java
new file mode 100644
index 00000000000..162866b9ee2
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/SelectCommand.java
@@ -0,0 +1,45 @@
+package seedu.mycrm.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+
+public class SelectCommand extends Command {
+ public static final String COMMAND_WORD = "select";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Only used in conjunction with addJob or editJob.\n"
+ + "Will select an item from a displayed list.\n"
+ + "depending on whether a contact or product is currently being assigned to the job\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private static final CommandType COMMAND_TYPE = CommandType.JOBS;
+
+ private final Index targetIndex;
+
+ public SelectCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ return stateManager.handleIndex(targetIndex);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof SelectCommand // instanceof handles nulls
+ && targetIndex.equals(((SelectCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/ThemeCommand.java b/src/main/java/seedu/mycrm/logic/commands/ThemeCommand.java
new file mode 100644
index 00000000000..1e7b0b967b8
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/ThemeCommand.java
@@ -0,0 +1,61 @@
+package seedu.mycrm.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+
+public class ThemeCommand extends Command {
+
+ public static final String COMMAND_WORD = "theme";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Change Ui theme.\n"
+ + "Example: " + COMMAND_WORD + " THEME_NAME";
+
+ public static final String MESSAGE_SUCCESS = "Change theme to %1$s";
+
+ public static final String MESSAGE_THEME_NOT_EXIST = "This theme does not exist in MyCRM";
+
+ private static final CommandType COMMAND_TYPE = CommandType.THEME;
+
+ private final String themeName;
+
+ /**
+ * Creates a ThemeCommand.
+ */
+ public ThemeCommand(String themeName) {
+ requireNonNull(themeName);
+
+ this.themeName = themeName;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ return new CommandResult(String.format(MESSAGE_SUCCESS, themeName),
+ COMMAND_TYPE,
+ themeName); // pass theme name to main window
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ if (o instanceof ThemeCommand) {
+ ThemeCommand cmd = (ThemeCommand) o;
+ return cmd.themeName.equals(this.themeName);
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/contacts/AddContactCommand.java b/src/main/java/seedu/mycrm/logic/commands/contacts/AddContactCommand.java
new file mode 100644
index 00000000000..05e86813972
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/contacts/AddContactCommand.java
@@ -0,0 +1,82 @@
+package seedu.mycrm.logic.commands.contacts;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_TAG;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.contact.Contact;
+
+/**
+ * Inserts a contact with name, phone, email, address or tags specified into the myCrm.
+ */
+public class AddContactCommand extends Command {
+
+ public static final String COMMAND_WORD = "addContact";
+
+ public static final Object MESSAGE_USAGE = COMMAND_WORD + ": Adds a contact to MyCRM. "
+ + "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 + "1st Tier "
+ + PREFIX_TAG + "Premium";
+
+ public static final Object MESSAGE_AT_LEAST_ONE_COMPONENT = "You have to give at least one info "
+ + "for this contact!!!";
+
+ public static final String MESSAGE_SUCCESS = "New contact added: %1$s\n";
+ public static final String MESSAGE_DUPLICATE_CONTACT = "This contact already exists in the MyCRM";
+
+ private static final CommandType COMMAND_TYPE = CommandType.CONTACTS;
+
+ private final Contact contactToAdd;
+
+ /**
+ * Creates an AddContact to add the specified {@code Contact}
+ */
+ public AddContactCommand(Contact contact) {
+ requireNonNull(contact);
+ contactToAdd = contact;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+
+ if (model.hasContact(contactToAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_CONTACT);
+ }
+
+ model.addContact(contactToAdd);
+ CommandResult commandResult = new CommandResult(String.format(MESSAGE_SUCCESS, contactToAdd), COMMAND_TYPE);
+ return stateManager.handleContact(contactToAdd, commandResult);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddContactCommand // instanceof handles nulls
+ && contactToAdd.equals(((AddContactCommand) other).contactToAdd));
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/contacts/DeleteContactCommand.java b/src/main/java/seedu/mycrm/logic/commands/contacts/DeleteContactCommand.java
new file mode 100644
index 00000000000..9e03c1a270d
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/contacts/DeleteContactCommand.java
@@ -0,0 +1,82 @@
+package seedu.mycrm.logic.commands.contacts;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_INCOMPLETE_JOBS;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_JOBS;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_NOT_HIDDEN_CONTACTS;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.contact.Contact;
+import seedu.mycrm.model.job.Job;
+
+/**
+ * Deletes a contact identified using it's displayed index from the myCrm.
+ */
+public class DeleteContactCommand extends Command {
+
+ public static final String COMMAND_WORD = "deleteContact";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the contact identified by the index number used in the displayed contact list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_CONTACT_SUCCESS = "Deleted Contact: %1$s";
+
+ private static final CommandType COMMAND_TYPE = CommandType.CONTACTS;
+
+ private final Index targetIndex;
+
+ public DeleteContactCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredContactList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+
+ Contact contactToDelete = lastShownList.get(targetIndex.getZeroBased());
+ Predicate latestJobPredicate = model.getLatestJobPredicate() == null ? PREDICATE_SHOW_ALL_INCOMPLETE_JOBS
+ : model.getLatestJobPredicate();
+ model.updateFilteredJobList(PREDICATE_SHOW_ALL_JOBS);
+ boolean isLinkedToJob = model.getFilteredJobList().stream()
+ .anyMatch(job -> job.getClient() != null && job.getClient().isSameContact(contactToDelete));
+ model.updateFilteredJobList(latestJobPredicate);
+
+ if (isLinkedToJob) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DELETE_REQUEST);
+ }
+
+ model.deleteContact(contactToDelete);
+ model.updateFilteredContactList(PREDICATE_SHOW_NOT_HIDDEN_CONTACTS);
+
+ return new CommandResult(String.format(MESSAGE_DELETE_CONTACT_SUCCESS, contactToDelete), COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteContactCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteContactCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/contacts/EditContactCommand.java b/src/main/java/seedu/mycrm/logic/commands/contacts/EditContactCommand.java
new file mode 100644
index 00000000000..9453e0607ee
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/contacts/EditContactCommand.java
@@ -0,0 +1,252 @@
+package seedu.mycrm.logic.commands.contacts;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_INCOMPLETE_JOBS;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_JOBS;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_NOT_HIDDEN_CONTACTS;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.commons.util.CollectionUtil;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.contact.Address;
+import seedu.mycrm.model.contact.Contact;
+import seedu.mycrm.model.contact.Email;
+import seedu.mycrm.model.contact.Name;
+import seedu.mycrm.model.contact.Phone;
+import seedu.mycrm.model.contact.tag.Tag;
+import seedu.mycrm.model.job.Job;
+
+/**
+ * Edits a contact identified using it's displayed index from the myCrm.
+ */
+public class EditContactCommand extends Command {
+
+ public static final String COMMAND_WORD = "editContact";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the contact identified "
+ + "by the index number used in the displayed contact list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "[" + PREFIX_NAME + "NAME] "
+ + "[" + PREFIX_PHONE + "PHONE] "
+ + "[" + PREFIX_EMAIL + "EMAIL] "
+ + "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "[" + PREFIX_TAG + "TAG]...\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_PHONE + "91234567 "
+ + PREFIX_EMAIL + "johndoe@example.com";
+
+ public static final String MESSAGE_EDIT_CONTACT_SUCCESS = "Edited Contact: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_CONTACT = "This contact already exists in MyCRM";
+
+ private static final CommandType COMMAND_TYPE = CommandType.CONTACTS;
+
+ private final Index index;
+ private final EditContactDescriptor editContactDescriptor;
+
+ /**
+ * @param index of the contact in the filtered contact list to edit
+ * @param editContactDescriptor details to edit the contact with
+ */
+ public EditContactCommand(Index index, EditContactDescriptor editContactDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editContactDescriptor);
+
+ this.index = index;
+ this.editContactDescriptor = new EditContactDescriptor(editContactDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredContactList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+
+ Contact contactToEdit = lastShownList.get(index.getZeroBased());
+ Contact editedContact = createEditedContact(contactToEdit, editContactDescriptor);
+
+ if (!contactToEdit.isSameContact(editedContact) && model.hasContact(editedContact)) {
+ throw new CommandException(MESSAGE_DUPLICATE_CONTACT);
+ }
+
+ model.setContact(contactToEdit, editedContact);
+ model.updateFilteredContactList(PREDICATE_SHOW_NOT_HIDDEN_CONTACTS);
+ Predicate latestJobPredicate = model.getLatestJobPredicate() == null ? PREDICATE_SHOW_ALL_INCOMPLETE_JOBS
+ : model.getLatestJobPredicate();
+ model.updateFilteredJobList(PREDICATE_SHOW_ALL_JOBS);
+ model.getFilteredJobList().stream()
+ .filter(job -> job.getClient() != null && job.getClient().isSameContact(contactToEdit))
+ .forEach(job -> {
+ job.setClient(editedContact);
+ model.setJob(job, job);
+ });
+ model.updateFilteredJobList(latestJobPredicate);
+ return new CommandResult(String.format(MESSAGE_EDIT_CONTACT_SUCCESS, editedContact), COMMAND_TYPE);
+ }
+
+ /**
+ * Creates and returns a {@code Contact} with the details of {@code contactToEdit}
+ * edited with {@code editContactDescriptor}.
+ */
+ private static Contact createEditedContact(Contact contactToEdit, EditContactDescriptor editContactDescriptor) {
+ assert contactToEdit != null;
+
+ Name updatedName = editContactDescriptor.getName().orElse(contactToEdit.getName());
+ Phone updatedPhone = editContactDescriptor.getPhone().orElse(contactToEdit.getPhone());
+ Email updatedEmail = editContactDescriptor.getEmail().orElse(contactToEdit.getEmail());
+ Address updatedAddress = editContactDescriptor.getAddress().orElse(contactToEdit.getAddress());
+ Set updatedTags = editContactDescriptor.getTags().orElse(contactToEdit.getTags());
+ boolean isHidden = contactToEdit.checkIsHidden();
+
+ return new Contact(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags, isHidden);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditContactCommand)) {
+ return false;
+ }
+
+ // state check
+ EditContactCommand e = (EditContactCommand) other;
+ return index.equals(e.index)
+ && editContactDescriptor.equals(e.editContactDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the contact with. Each non-empty field value will replace the
+ * corresponding field value of the contact.
+ */
+ public static class EditContactDescriptor {
+ private Name name;
+ private Phone phone;
+ private Email email;
+ private Address address;
+ private Set tags;
+
+ public EditContactDescriptor() {}
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditContactDescriptor(EditContactDescriptor toCopy) {
+ setName(toCopy.name);
+ setPhone(toCopy.phone);
+ setEmail(toCopy.email);
+ setAddress(toCopy.address);
+ setTags(toCopy.tags);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
+ }
+
+ public void setName(Name name) {
+ this.name = name;
+ }
+
+ public Optional getName() {
+ return Optional.ofNullable(name);
+ }
+
+ public void setPhone(Phone phone) {
+ this.phone = phone;
+ }
+
+ public Optional getPhone() {
+ return Optional.ofNullable(phone);
+ }
+
+ public void setEmail(Email email) {
+ this.email = email;
+ }
+
+ public Optional getEmail() {
+ return Optional.ofNullable(email);
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ 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;
+ }
+
+ /**
+ * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code tags} is null.
+ */
+ public Optional> getTags() {
+ return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditContactDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditContactDescriptor e = (EditContactDescriptor) other;
+
+ return getName().equals(e.getName())
+ && getPhone().equals(e.getPhone())
+ && getEmail().equals(e.getEmail())
+ && getAddress().equals(e.getAddress())
+ && getTags().equals(e.getTags());
+ }
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/contacts/FindContactCommand.java b/src/main/java/seedu/mycrm/logic/commands/contacts/FindContactCommand.java
new file mode 100644
index 00000000000..12b328a9094
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/contacts/FindContactCommand.java
@@ -0,0 +1,53 @@
+package seedu.mycrm.logic.commands.contacts;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.contact.NameContainsKeywordsPredicate;
+
+/**
+ * Finds and lists all contacts in myCrm whose name contains any of the argument keywords.
+ * Keyword matching is case-insensitive.
+ */
+public class FindContactCommand extends Command {
+
+ public static final String COMMAND_WORD = "findContact";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all contacts 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 static final CommandType COMMAND_TYPE = CommandType.CONTACTS;
+
+ private final NameContainsKeywordsPredicate predicate;
+
+ public FindContactCommand(NameContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ requireNonNull(model);
+ model.updateFilteredContactList(predicate);
+ return stateManager.handleList(new CommandResult(String.format(Messages.MESSAGE_CONTACTS_LISTED_OVERVIEW,
+ model.getFilteredContactList().size()), COMMAND_TYPE));
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindContactCommand // instanceof handles nulls
+ && predicate.equals(((FindContactCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/contacts/HideContactCommand.java b/src/main/java/seedu/mycrm/logic/commands/contacts/HideContactCommand.java
new file mode 100644
index 00000000000..6cfa964f290
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/contacts/HideContactCommand.java
@@ -0,0 +1,78 @@
+package seedu.mycrm.logic.commands.contacts;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_NOT_HIDDEN_CONTACTS;
+
+import java.util.List;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.contact.Contact;
+
+/**
+ * Hides a contact identified using it's displayed index from the myCrm.
+ */
+public class HideContactCommand extends Command {
+ public static final String COMMAND_WORD = "hideContact";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Hides the details of the contact identified "
+ + "by the index number used in the displayed contact list.\n"
+ + "Existing contact info will be hidden.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "Example: " + COMMAND_WORD + " 1 ";
+
+ public static final String MESSAGE_HIDE_CONTACT_SUCCESS = "Hidden Contact: %1$s";
+
+ private static final CommandType COMMAND_TYPE = CommandType.CONTACTS;
+
+ private final Index targetIndex;
+
+ /**
+ * @param targetIndex of the contact in the filtered contact list to edit
+ **/
+ public HideContactCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredContactList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+
+ Contact contactToHide = lastShownList.get(targetIndex.getZeroBased());
+ String successMessage;
+
+ if (contactToHide.checkIsHidden()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_HIDE_REQUEST);
+ }
+
+ successMessage = String.format(MESSAGE_HIDE_CONTACT_SUCCESS, contactToHide);
+ model.hideContact(contactToHide);
+ model.updateFilteredContactList(PREDICATE_SHOW_NOT_HIDDEN_CONTACTS);
+ return new CommandResult(successMessage, COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof HideContactCommand // instanceof handles nulls
+ && targetIndex.equals(((HideContactCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/contacts/ListContactCommand.java b/src/main/java/seedu/mycrm/logic/commands/contacts/ListContactCommand.java
new file mode 100644
index 00000000000..cad6da16ce0
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/contacts/ListContactCommand.java
@@ -0,0 +1,76 @@
+package seedu.mycrm.logic.commands.contacts;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_CONTACTS;
+
+import java.util.function.Predicate;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.contact.Contact;
+
+/**
+ * Lists all contacts in the myCrm to the user.
+ */
+public class ListContactCommand extends Command {
+
+ public static final String COMMAND_WORD = "listContact";
+
+ public static final String MESSAGE_SUCCESS_ALL = "Here are the contacts all listed:";
+ public static final String MESSAGE_SUCCESS_NOT_HIDDEN = "Here are the active contacts listed:";
+ public static final String SHOW_ALL_CONTACTS = "-a";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all unhidden contact by default. "
+ + "If a user types in '-a', MyCRM will also list hidden contact. "
+ + "Parameters: [-a]\n"
+ + "Example: " + COMMAND_WORD + SHOW_ALL_CONTACTS;;
+
+ private static final CommandType COMMAND_TYPE = CommandType.CONTACTS;
+
+ private final Predicate listPredicate;
+
+ /**
+ * Constructor for listContactCommand.
+ * If no predicate given, by default listContactCommand will list all commands.
+ */
+ public ListContactCommand() {
+ this.listPredicate = PREDICATE_SHOW_ALL_CONTACTS;
+ }
+
+ /**
+ * Constructor for listContactCommand.
+ * List all contacts in the list with given predicate.
+ *
+ * @param listPredicate
+ */
+ public ListContactCommand(Predicate listPredicate) {
+ this.listPredicate = listPredicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ requireNonNull(model);
+ model.updateFilteredContactList(listPredicate);
+ String successMessage;
+ if (listPredicate.equals(PREDICATE_SHOW_ALL_CONTACTS)) {
+ successMessage = MESSAGE_SUCCESS_ALL;
+ } else {
+ successMessage = MESSAGE_SUCCESS_NOT_HIDDEN;
+ }
+ return stateManager.handleList(new CommandResult(successMessage, COMMAND_TYPE));
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ListContactCommand // instanceof handles nulls
+ && listPredicate.equals(((ListContactCommand) other).listPredicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/contacts/UndoHideContactCommand.java b/src/main/java/seedu/mycrm/logic/commands/contacts/UndoHideContactCommand.java
new file mode 100644
index 00000000000..6299850991d
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/contacts/UndoHideContactCommand.java
@@ -0,0 +1,79 @@
+package seedu.mycrm.logic.commands.contacts;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_NOT_HIDDEN_CONTACTS;
+
+import java.util.List;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.contact.Contact;
+
+/**
+ * Undoes hiding a contact identified using it's displayed index from the myCrm.
+ */
+public class UndoHideContactCommand extends Command {
+ public static final String COMMAND_WORD = "undoHideContact";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Undo Hiding the details of the contact identified "
+ + "by the index number used in the displayed contact list.\n"
+ + "Existing contact info will be hidden.\n"
+ + "User must call listContact -a in order to see hidden contacts.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "Example: " + COMMAND_WORD + " 1 ";
+
+ public static final String MESSAGE_UNDO_HIDE_CONTACT_SUCCESS = "Undo hiding Contact: %1$s";
+
+ private static final CommandType COMMAND_TYPE = CommandType.CONTACTS;
+
+ private final Index targetIndex;
+
+ /**
+ * @param targetIndex of the contact in the filtered contact list to edit
+ **/
+ public UndoHideContactCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredContactList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+
+ Contact contactToUndoHide = lastShownList.get(targetIndex.getZeroBased());
+ String successMessage;
+
+ if (!contactToUndoHide.checkIsHidden()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_UNDO_HIDE_REQUEST);
+ }
+
+ successMessage = String.format(MESSAGE_UNDO_HIDE_CONTACT_SUCCESS, contactToUndoHide);
+ model.undoHideContact(contactToUndoHide);
+ model.updateFilteredContactList(PREDICATE_SHOW_NOT_HIDDEN_CONTACTS);
+ return new CommandResult(successMessage, COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof UndoHideContactCommand // instanceof handles nulls
+ && targetIndex.equals(((UndoHideContactCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/mycrm/logic/commands/exceptions/CommandException.java
similarity index 82%
rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
rename to src/main/java/seedu/mycrm/logic/commands/exceptions/CommandException.java
index a16bd14f2cd..595c84c3824 100644
--- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
+++ b/src/main/java/seedu/mycrm/logic/commands/exceptions/CommandException.java
@@ -1,4 +1,6 @@
-package seedu.address.logic.commands.exceptions;
+package seedu.mycrm.logic.commands.exceptions;
+
+import seedu.mycrm.logic.commands.Command;
/**
* Represents an error which occurs during execution of a {@link Command}.
diff --git a/src/main/java/seedu/mycrm/logic/commands/history/ClearHistoryCommand.java b/src/main/java/seedu/mycrm/logic/commands/history/ClearHistoryCommand.java
new file mode 100644
index 00000000000..c257a3021ac
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/history/ClearHistoryCommand.java
@@ -0,0 +1,30 @@
+package seedu.mycrm.logic.commands.history;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.model.Model;
+
+
+public class ClearHistoryCommand extends Command {
+ public static final String COMMAND_WORD = "clearHistory";
+
+ public static final String MESSAGE_SUCCESS = "History command data has been cleared";
+
+ private static final CommandType COMMAND_TYPE = CommandType.HISTORY;
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ requireNonNull(model);
+ model.clearHistory();
+ return new CommandResult(MESSAGE_SUCCESS, COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/history/HistoryCommand.java b/src/main/java/seedu/mycrm/logic/commands/history/HistoryCommand.java
new file mode 100644
index 00000000000..19e1e502149
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/history/HistoryCommand.java
@@ -0,0 +1,30 @@
+package seedu.mycrm.logic.commands.history;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_HISTORIES;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.model.Model;
+
+public class HistoryCommand extends Command {
+ public static final String COMMAND_WORD = "history";
+
+ public static final String SHOWING_HISTORY_MESSAGE = "Commands history are shown";
+
+ private static final CommandType COMMAND_TYPE = CommandType.HISTORY;
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ requireNonNull(model);
+ model.updateFilteredHistoryList(PREDICATE_SHOW_ALL_HISTORIES);
+ return new CommandResult(SHOWING_HISTORY_MESSAGE, COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/jobs/AddJobCommand.java b/src/main/java/seedu/mycrm/logic/commands/jobs/AddJobCommand.java
new file mode 100644
index 00000000000..59d559cfbd2
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/jobs/AddJobCommand.java
@@ -0,0 +1,118 @@
+package seedu.mycrm.logic.commands.jobs;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_JOB_EXPECTED_COMPLETION_DATE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_CONTACT_INDEX;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_EXPECTED_COMPLETION_DATE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_FEE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_JOB_DESCRIPTION;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_INDEX;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_RECEIVED_DATE;
+
+import java.util.List;
+import java.util.Objects;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.contact.Contact;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.product.Product;
+
+public class AddJobCommand extends Command {
+ public static final String COMMAND_WORD = "addJob";
+
+ public static final Object MESSAGE_USAGE = COMMAND_WORD + ": Adds a repair job to MyCRM.\n"
+ + "Parameters: "
+ + PREFIX_JOB_DESCRIPTION + "JOB DESCRIPTION "
+ + PREFIX_EXPECTED_COMPLETION_DATE + "EXPECTED COMPLETION DATE "
+ + PREFIX_FEE + "FEE "
+ + " [" + PREFIX_RECEIVED_DATE + "RECEIVED DATE]"
+ + " [" + PREFIX_CONTACT_INDEX + "CONTACT INDEX] "
+ + " [" + PREFIX_PRODUCT_INDEX + "PRODUCT INDEX] \n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_JOB_DESCRIPTION + "CPU card replacement needed "
+ + PREFIX_CONTACT_INDEX + "1 "
+ + PREFIX_PRODUCT_INDEX + "1 "
+ + PREFIX_EXPECTED_COMPLETION_DATE + "10/01/2022 "
+ + PREFIX_FEE + "$30.00";
+
+ public static final String MESSAGE_SUCCESS = "New repair job added: %1$s\n";
+
+ public static final String MESSAGE_DUPLICATE_JOB = "This repair job already exists in the MyCRM";
+
+ private static final CommandType COMMAND_TYPE = CommandType.JOBS;
+
+ private final Job toAdd;
+ private final Index contactIndex;
+ private final Index productIndex;
+
+ /**
+ * Creates an AddJobCommand to add the specified {@code Job}
+ */
+ public AddJobCommand(Job job, Index contactIndex, Index productIndex) {
+ requireNonNull(job);
+ this.contactIndex = contactIndex;
+ this.productIndex = productIndex;
+ toAdd = job;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+
+ if (toAdd.getExpectedCompletionDate().value.isBefore(toAdd.getReceivedDate().value)) {
+ throw new CommandException(MESSAGE_INVALID_JOB_EXPECTED_COMPLETION_DATE);
+ }
+
+ linkContactToJob(toAdd, model.getFilteredContactList());
+ linkProductToJob(toAdd, model.getFilteredProductList());
+
+ if (model.hasJob(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_JOB);
+ }
+
+ return stateManager.handleAddJob(toAdd);
+ }
+
+ private void linkContactToJob(Job job, List lastShownContactList) throws CommandException {
+ if (contactIndex != null) {
+ if (contactIndex.getZeroBased() >= lastShownContactList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+
+ Contact client = lastShownContactList.get(contactIndex.getZeroBased());
+ job.setClient(client);
+ }
+ }
+
+ private void linkProductToJob(Job job, List lastShownProductList) throws CommandException {
+ if (productIndex != null) {
+ if (productIndex.getZeroBased() >= lastShownProductList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX);
+ }
+
+ Product product = lastShownProductList.get(productIndex.getZeroBased());
+ job.setProduct(product);
+ }
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddJobCommand // instanceof handles nulls
+ && toAdd.equals(((AddJobCommand) other).toAdd))
+ && Objects.equals(contactIndex, ((AddJobCommand) other).contactIndex)
+ && Objects.equals(productIndex, ((AddJobCommand) other).productIndex);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/jobs/CompleteJobCommand.java b/src/main/java/seedu/mycrm/logic/commands/jobs/CompleteJobCommand.java
new file mode 100644
index 00000000000..2dea0941b89
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/jobs/CompleteJobCommand.java
@@ -0,0 +1,84 @@
+package seedu.mycrm.logic.commands.jobs;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.job.JobDate;
+
+public class CompleteJobCommand extends Command {
+ public static final String COMMAND_WORD = "completeJob";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + "Marks the job identified by the index number in the displayed job list as complete.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "[COMPLETION DATE (in dd/MM/YYYY)]\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_SUCCESS = "Repair job marked as complete: %1$s";
+
+ private static final CommandType COMMAND_TYPE = CommandType.JOBS;
+
+ private final Index targetIndex;
+ private final JobDate completionDate;
+
+ /**
+ * Constructs a CompleteJobCommand object
+ * @param targetIndex Index of job from displayed list that should be marked complete
+ * @param completionDate Date job was completed
+ */
+ public CompleteJobCommand(Index targetIndex, JobDate completionDate) {
+ this.targetIndex = targetIndex;
+ this.completionDate = completionDate;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+
+ List lastShownList = model.getFilteredJobList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_JOB_DISPLAYED_INDEX);
+ }
+
+ Job jobToMarkComplete = lastShownList.get(targetIndex.getZeroBased());
+
+ if (jobToMarkComplete.isCompleted()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_JOB_COMPLETE_REQUEST);
+ }
+
+ if (completionDate.value.isBefore(jobToMarkComplete.getReceivedDate().value)) {
+ throw new CommandException(Messages.MESSAGE_INVALID_JOB_COMPLETION_DATE);
+ }
+
+ Job copiedJob = new Job(jobToMarkComplete);
+ copiedJob.markCompleted(completionDate);
+
+ model.setJob(jobToMarkComplete, copiedJob);
+ model.updateFilteredJobList(Model.PREDICATE_SHOW_ALL_INCOMPLETE_JOBS);
+
+ return new CommandResult(String.format(MESSAGE_SUCCESS, jobToMarkComplete), COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof CompleteJobCommand // instanceof handles nulls
+ && targetIndex.equals(((CompleteJobCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/jobs/DeleteJobCommand.java b/src/main/java/seedu/mycrm/logic/commands/jobs/DeleteJobCommand.java
new file mode 100644
index 00000000000..b40f34fed87
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/jobs/DeleteJobCommand.java
@@ -0,0 +1,60 @@
+package seedu.mycrm.logic.commands.jobs;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.job.Job;
+
+public class DeleteJobCommand extends Command {
+ public static final String COMMAND_WORD = "deleteJob";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the job identified by the index number used in the displayed job list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_JOB_SUCCESS = "Deleted Job: %1$s";
+
+ private static final CommandType COMMAND_TYPE = CommandType.JOBS;
+
+ private final Index targetIndex;
+
+ public DeleteJobCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredJobList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_JOB_DISPLAYED_INDEX);
+ }
+
+ Job jobToDelete = lastShownList.get(targetIndex.getZeroBased());
+ model.deleteJob(jobToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_JOB_SUCCESS, jobToDelete), COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteJobCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteJobCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/jobs/EditJobCommand.java b/src/main/java/seedu/mycrm/logic/commands/jobs/EditJobCommand.java
new file mode 100644
index 00000000000..af7a0be1fb6
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/jobs/EditJobCommand.java
@@ -0,0 +1,288 @@
+package seedu.mycrm.logic.commands.jobs;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_CONTACT_INDEX;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_EXPECTED_COMPLETION_DATE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_FEE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_JOB_DESCRIPTION;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_INDEX;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_RECEIVED_DATE;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.commons.util.CollectionUtil;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.contact.Contact;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.job.JobDate;
+import seedu.mycrm.model.job.JobDescription;
+import seedu.mycrm.model.job.JobFee;
+import seedu.mycrm.model.job.JobStatus;
+import seedu.mycrm.model.product.Product;
+
+public class EditJobCommand extends Command {
+ public static final String COMMAND_WORD = "editJob";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the repair job identified "
+ + "by the index number used in the displayed job list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "[" + PREFIX_JOB_DESCRIPTION + "JOB DESCRIPTION] "
+ + "[" + PREFIX_FEE + "FEE] "
+ + "[" + PREFIX_EXPECTED_COMPLETION_DATE + "EXPECTED COMPLETION DATE] "
+ + "[" + PREFIX_RECEIVED_DATE + "RECEIVED DATE] "
+ + "[" + PREFIX_CONTACT_INDEX + "CONTACT INDEX] "
+ + "[" + PREFIX_PRODUCT_INDEX + "PRODUCT INDEX]\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_JOB_DESCRIPTION + "Repair laptop screen "
+ + PREFIX_FEE + "$50.00 ";
+
+ public static final String MESSAGE_EDIT_JOB_SUCCESS = "Repair job edited: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_JOB = "This repair job already exists in the MyCRM";
+
+ private static final CommandType COMMAND_TYPE = CommandType.JOBS;
+
+ private final Index index;
+ private final EditJobDescriptor editJobDescriptor;
+
+ /**
+ * @param index of the repair job in the filtered job list to edit
+ * @param editJobDescriptor details to edit the job with
+ */
+ public EditJobCommand(Index index, EditJobCommand.EditJobDescriptor editJobDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editJobDescriptor);
+
+ this.index = index;
+ this.editJobDescriptor = new EditJobCommand.EditJobDescriptor(editJobDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ List lastShownJobList = model.getFilteredJobList();
+ List lastShownContactList = model.getFilteredContactList();
+ List lastShownProductList = model.getFilteredProductList();
+
+ if (index.getZeroBased() >= lastShownJobList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_JOB_DISPLAYED_INDEX);
+ }
+
+ Job jobToEdit = lastShownJobList.get(index.getZeroBased());
+ Job editedJob = createEditedJob(jobToEdit, editJobDescriptor, lastShownContactList, lastShownProductList);
+
+
+ if (editedJob.getExpectedCompletionDate().value.isBefore(editedJob.getReceivedDate().value)) {
+ throw new CommandException(Messages.MESSAGE_INVALID_JOB_EXPECTED_COMPLETION_DATE);
+ }
+
+ if (editedJob.isCompleted() && editedJob.getCompletionDate().value.isBefore(
+ editedJob.getReceivedDate().value)) {
+ throw new CommandException(Messages.MESSAGE_INVALID_JOB_COMPLETION_DATE);
+ }
+
+ if (!jobToEdit.isSameJob(editedJob) && model.hasJob(editedJob)) {
+ throw new CommandException(MESSAGE_DUPLICATE_JOB);
+ }
+
+ CommandResult result = new CommandResult(String.format(MESSAGE_EDIT_JOB_SUCCESS, editedJob), COMMAND_TYPE);
+
+ return stateManager.handleEditJob(jobToEdit, editedJob, editJobDescriptor.shouldEditContact,
+ editJobDescriptor.shouldEditProduct, result);
+ }
+
+ /**
+ * Creates and returns a {@code Job} with the details of {@code jobToEdit}
+ * edited with {@code editJobDescriptor}.
+ */
+ private static Job createEditedJob(Job jobToEdit, EditJobCommand.EditJobDescriptor editJobDescriptor,
+ List lastShownContactList, List lastShownProductList) throws CommandException {
+ assert jobToEdit != null;
+
+ JobDescription updatedJobDescription = editJobDescriptor.getJobDescription()
+ .orElse(jobToEdit.getJobDescription());
+ JobDate updatedExpectedCompletionDate = editJobDescriptor.getExpectedCompletionDate().orElse(
+ jobToEdit.getExpectedCompletionDate());
+ JobDate updatedReceivedDate = editJobDescriptor.getReceivedDate().orElse(jobToEdit.getReceivedDate());
+ JobFee updatedFee = editJobDescriptor.getFee().orElse(jobToEdit.getFee());
+ JobStatus jobStatus = jobToEdit.getJobStatus();
+ JobDate completionDate = jobToEdit.getCompletionDate();
+
+ Index clientIndex = editJobDescriptor.getClientIndex();
+ Contact updatedClient = jobToEdit.getClient();
+
+ if (clientIndex != null) {
+ if (clientIndex.getZeroBased() >= lastShownContactList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+
+ updatedClient = lastShownContactList.get(clientIndex.getZeroBased());
+ }
+
+ Index productIndex = editJobDescriptor.getProductIndex();
+ Product updatedProduct = jobToEdit.getProduct();
+
+ if (productIndex != null) {
+ if (productIndex.getZeroBased() >= lastShownProductList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX);
+ }
+
+ updatedProduct = lastShownProductList.get(productIndex.getZeroBased());
+ }
+
+ return new Job(updatedJobDescription, updatedClient, updatedProduct, updatedExpectedCompletionDate,
+ jobStatus, updatedReceivedDate, completionDate, updatedFee);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditJobCommand)) {
+ return false;
+ }
+
+ // state check
+ EditJobCommand e = (EditJobCommand) other;
+ return index.equals(e.index)
+ && editJobDescriptor.equals(e.editJobDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the job with. Each non-empty field value will replace the
+ * corresponding field value of the job.
+ */
+ public static class EditJobDescriptor {
+ private JobDescription jobDescription;
+ private JobDate expectedCompletionDate;
+ private JobDate receivedDate;
+ private JobFee fee;
+ private Index clientIndex;
+ private Index productIndex;
+ private boolean shouldEditContact = false;
+ private boolean shouldEditProduct = false;
+
+ public EditJobDescriptor() {}
+
+ /**
+ * Copy constructor.
+ */
+ public EditJobDescriptor(EditJobCommand.EditJobDescriptor toCopy) {
+ setJobDescription(toCopy.jobDescription);
+ setExpectedCompletionDate(toCopy.expectedCompletionDate);
+ setReceivedDate(toCopy.receivedDate);
+ setFee(toCopy.fee);
+ setClientIndex(toCopy.clientIndex);
+ setProductIndex(toCopy.productIndex);
+ setEditContact(toCopy.shouldEditContact);
+ setEditProduct(toCopy.shouldEditProduct);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(jobDescription, expectedCompletionDate, receivedDate,
+ fee, clientIndex, productIndex) || shouldEditProduct || shouldEditContact;
+ }
+
+ public void setJobDescription(JobDescription jobDescription) {
+ this.jobDescription = jobDescription;
+ }
+
+ public Optional getJobDescription() {
+ return Optional.ofNullable(jobDescription);
+ }
+
+ public void setExpectedCompletionDate(JobDate expectedCompletionDate) {
+ this.expectedCompletionDate = expectedCompletionDate;
+ }
+
+ public Optional getExpectedCompletionDate() {
+ return Optional.ofNullable(expectedCompletionDate);
+ }
+
+ public void setReceivedDate(JobDate receivedDate) {
+ this.receivedDate = receivedDate;
+ }
+
+ public Optional getReceivedDate() {
+ return Optional.ofNullable(receivedDate);
+ }
+
+ public void setFee(JobFee fee) {
+ this.fee = fee;
+ }
+
+ public Optional getFee() {
+ return Optional.ofNullable(fee);
+ }
+
+ public void setClientIndex (Index clientIndex) {
+ this.clientIndex = clientIndex;
+ }
+
+ public Index getClientIndex() {
+ return clientIndex;
+ }
+
+ public void setProductIndex (Index productIndex) {
+ this.productIndex = productIndex;
+ }
+
+ public Index getProductIndex() {
+ return productIndex;
+ }
+
+ public void setEditProduct(boolean shouldEditProduct) {
+ this.shouldEditProduct = shouldEditProduct;
+ }
+
+ public void setEditContact(boolean shouldEditContact) {
+ this.shouldEditContact = shouldEditContact;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditJobCommand.EditJobDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditJobCommand.EditJobDescriptor e = (EditJobCommand.EditJobDescriptor) other;
+ return getJobDescription().equals(e.getJobDescription())
+ && getExpectedCompletionDate().equals(e.getExpectedCompletionDate())
+ && getReceivedDate().equals(e.getReceivedDate())
+ && getFee().equals(e.getFee())
+ && Objects.equals(getClientIndex(), e.getClientIndex())
+ && Objects.equals(getProductIndex(), e.getProductIndex())
+ && shouldEditContact == e.shouldEditContact
+ && shouldEditProduct == e.shouldEditProduct;
+ }
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/jobs/FindJobCommand.java b/src/main/java/seedu/mycrm/logic/commands/jobs/FindJobCommand.java
new file mode 100644
index 00000000000..e7f2a3bbf02
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/jobs/FindJobCommand.java
@@ -0,0 +1,48 @@
+package seedu.mycrm.logic.commands.jobs;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.job.JobContainsKeywordsPredicate;
+
+public class FindJobCommand extends Command {
+ public static final String COMMAND_WORD = "findJob";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all jobs whose descriptions 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 + " Graphics card replacement needed";
+
+ private static final CommandType COMMAND_TYPE = CommandType.JOBS;
+
+ private final JobContainsKeywordsPredicate predicate;
+
+ public FindJobCommand(JobContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ requireNonNull(model);
+ model.updateFilteredJobList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_JOBS_LISTED_OVERVIEW, model.getFilteredJobList().size()), COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindJobCommand // instanceof handles nulls
+ && predicate.equals(((FindJobCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/jobs/ListJobCommand.java b/src/main/java/seedu/mycrm/logic/commands/jobs/ListJobCommand.java
new file mode 100644
index 00000000000..c1b727138ab
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/jobs/ListJobCommand.java
@@ -0,0 +1,71 @@
+package seedu.mycrm.logic.commands.jobs;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.function.Predicate;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.job.Job;
+
+public class ListJobCommand extends Command {
+ public static final String SHOW_ALL_FLAG = "-a";
+ public static final String SHOW_COMPLETED_FLAG = "-c";
+
+ public static final String COMMAND_WORD = "listJob";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Lists all in-progress jobs by default\n"
+ + "Flags to modify list of jobs displayed (only one flag allowed at a time): \n"
+ + SHOW_ALL_FLAG + ": to show all jobs\n"
+ + SHOW_COMPLETED_FLAG + ": to show only completed jobs\n"
+ + "Example: " + COMMAND_WORD + " -a";
+
+ public static final String MESSAGE_SUCCESS_ALL = "Listed all jobs";
+ public static final String MESSAGE_SUCCESS_ONLY_COMPLETED = "Listed all completed jobs";
+ public static final String MESSAGE_SUCCESS_ONLY_PENDING = "Listed all in-progress jobs";
+
+ private static final CommandType COMMAND_TYPE = CommandType.JOBS;
+
+ private final Predicate listPredicate;
+
+ /**
+ * Creates an ListJobCommand to list jobs matching the {@code listPredicate}.
+ */
+ public ListJobCommand(Predicate listPredicate) {
+ requireNonNull(listPredicate);
+ this.listPredicate = listPredicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+
+ model.updateFilteredJobList(listPredicate);
+ String userFeedback = MESSAGE_SUCCESS_ALL;
+
+ if (listPredicate == Model.PREDICATE_SHOW_ALL_COMPLETED_JOBS) {
+ userFeedback = MESSAGE_SUCCESS_ONLY_COMPLETED;
+ } else if (listPredicate == Model.PREDICATE_SHOW_ALL_INCOMPLETE_JOBS) {
+ userFeedback = MESSAGE_SUCCESS_ONLY_PENDING;
+ }
+
+ return new CommandResult(userFeedback, COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ListJobCommand // instanceof handles nulls
+ && listPredicate.equals(((ListJobCommand) other).listPredicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/jobs/UndoCompleteJobCommand.java b/src/main/java/seedu/mycrm/logic/commands/jobs/UndoCompleteJobCommand.java
new file mode 100644
index 00000000000..488ef5b5e5b
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/jobs/UndoCompleteJobCommand.java
@@ -0,0 +1,70 @@
+package seedu.mycrm.logic.commands.jobs;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.job.Job;
+
+public class UndoCompleteJobCommand extends Command {
+ public static final String COMMAND_WORD = "undoCompleteJob";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + "Reverts the status of the completed job identified by the INDEX "
+ + "in the displayed job list to incomplete.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_SUCCESS = "Repair job's status reverted to in-progress: %1$s";
+
+ private static final CommandType COMMAND_TYPE = CommandType.JOBS;
+
+ private final Index targetIndex;
+
+ public UndoCompleteJobCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredJobList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_JOB_DISPLAYED_INDEX);
+ }
+
+ Job jobToRevertStatus = lastShownList.get(targetIndex.getZeroBased());
+
+ if (!jobToRevertStatus.isCompleted()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_JOB_UNDO_COMPLETE_REQUEST);
+ }
+
+ Job copiedJob = new Job(jobToRevertStatus);
+ copiedJob.markIncomplete();
+
+ model.setJob(jobToRevertStatus, copiedJob);
+ model.updateFilteredJobList(Model.PREDICATE_SHOW_ALL_INCOMPLETE_JOBS);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, jobToRevertStatus), COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof UndoCompleteJobCommand // instanceof handles nulls
+ && targetIndex.equals(((UndoCompleteJobCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/mails/AddTemplateCommand.java b/src/main/java/seedu/mycrm/logic/commands/mails/AddTemplateCommand.java
new file mode 100644
index 00000000000..c026d147111
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/mails/AddTemplateCommand.java
@@ -0,0 +1,68 @@
+package seedu.mycrm.logic.commands.mails;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_BODY;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_SUBJECT;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.mail.Template;
+
+/**
+ * Adds a template to the myCrm.
+ */
+public class AddTemplateCommand extends Command {
+
+ public static final String COMMAND_WORD = "addTemplate";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a template to the myCrm. "
+ + "Parameters: "
+ + PREFIX_SUBJECT + "SUBJECT "
+ + PREFIX_BODY + "BODY\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_SUBJECT + "Completed Job "
+ + PREFIX_BODY + "Hello, your product is ready! ";
+
+ public static final String MESSAGE_SUCCESS = "New template added: %1$s";
+ public static final String MESSAGE_DUPLICATE_TEMPLATE = "This template already exists in the myCrm";
+
+ private static final CommandType COMMAND_TYPE = CommandType.TEMPLATE;
+
+ private final Template toAdd;
+
+ /**
+ * Creates an AddTemplateCommand to add the specified {@code Template}
+ */
+ public AddTemplateCommand(Template template) {
+ requireNonNull(template);
+ toAdd = template;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+
+ if (model.hasTemplate(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TEMPLATE);
+ }
+
+ model.addTemplate(toAdd);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof AddTemplateCommand
+ && toAdd.equals(((AddTemplateCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/mails/DeleteTemplateCommand.java b/src/main/java/seedu/mycrm/logic/commands/mails/DeleteTemplateCommand.java
new file mode 100644
index 00000000000..a0efd15d68b
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/mails/DeleteTemplateCommand.java
@@ -0,0 +1,64 @@
+package seedu.mycrm.logic.commands.mails;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.mail.Template;
+
+/**
+ * Deletes a template identified using it's displayed index from the myCrm.
+ */
+public class DeleteTemplateCommand extends Command {
+
+ public static final String COMMAND_WORD = "deleteTemplate";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the template identified by the index number used in the displayed template list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_TEMPLATE_SUCCESS = "Deleted Template: %1$s";
+
+ private static final CommandType COMMAND_TYPE = CommandType.TEMPLATE;
+
+ private final Index targetIndex;
+
+ public DeleteTemplateCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTemplateList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TEMPLATE_DISPLAYED_INDEX);
+ }
+
+ Template templateToDelete = lastShownList.get(targetIndex.getZeroBased());
+ model.deleteTemplate(templateToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_TEMPLATE_SUCCESS, templateToDelete), COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof DeleteTemplateCommand
+ && targetIndex.equals(((DeleteTemplateCommand) other).targetIndex));
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/mails/EditTemplateCommand.java b/src/main/java/seedu/mycrm/logic/commands/mails/EditTemplateCommand.java
new file mode 100644
index 00000000000..fabc4c365ce
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/mails/EditTemplateCommand.java
@@ -0,0 +1,166 @@
+package seedu.mycrm.logic.commands.mails;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_BODY;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_SUBJECT;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_TEMPLATES;
+
+import java.util.List;
+import java.util.Optional;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.commons.util.CollectionUtil;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.mail.Body;
+import seedu.mycrm.model.mail.Subject;
+import seedu.mycrm.model.mail.Template;
+
+/**
+ * Edits a template identified using it's displayed index from the myCrm.
+ */
+public class EditTemplateCommand extends Command {
+
+ public static final String COMMAND_WORD = "editTemplate";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the template identified "
+ + "by the index number used in the displayed template list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "[" + PREFIX_SUBJECT + "SUBJECT] "
+ + "[" + PREFIX_BODY + "BODY]\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_SUBJECT + "Alert "
+ + PREFIX_BODY + "Your repair product requires attention!";
+
+ public static final String MESSAGE_EDIT_TEMPLATE_SUCCESS = "Edited Template: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_TEMPLATE = "This template already exists in MyCRM";
+
+ private static final CommandType COMMAND_TYPE = CommandType.TEMPLATE;
+
+ private final Index index;
+ private final EditTemplateDescriptor editTemplateDescriptor;
+
+ /**
+ * @param index of the template in the filtered template list to edit
+ * @param editTemplateDescriptor details to edit the template with
+ */
+ public EditTemplateCommand(Index index, EditTemplateDescriptor editTemplateDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editTemplateDescriptor);
+
+ this.index = index;
+ this.editTemplateDescriptor = new EditTemplateDescriptor(editTemplateDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTemplateList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TEMPLATE_DISPLAYED_INDEX);
+ }
+
+ Template templateToEdit = lastShownList.get(index.getZeroBased());
+ Template editedTemplate = createEditedTemplate(templateToEdit, editTemplateDescriptor);
+
+ if (!templateToEdit.isSameTemplate(editedTemplate) && model.hasTemplate(editedTemplate)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TEMPLATE);
+ }
+
+ model.setTemplate(templateToEdit, editedTemplate);
+ model.updateFilteredTemplateList(PREDICATE_SHOW_ALL_TEMPLATES);
+ return new CommandResult(String.format(MESSAGE_EDIT_TEMPLATE_SUCCESS, editedTemplate), COMMAND_TYPE);
+ }
+
+ /**
+ * Creates and returns a {@code Template} with the details of {@code templateToEdit}
+ * edited with {@code editTemplateDescriptor}.
+ */
+ private static Template createEditedTemplate(Template templateToEdit,
+ EditTemplateDescriptor editTemplateDescriptor) {
+ assert templateToEdit != null;
+
+ Subject updatedSubject = editTemplateDescriptor.getSubject().orElse(templateToEdit.getSubject());
+ Body updatedBody = editTemplateDescriptor.getBody().orElse(templateToEdit.getBody());
+
+ return new Template(updatedSubject, updatedBody);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof EditTemplateCommand)) {
+ return false;
+ }
+
+ EditTemplateCommand e = (EditTemplateCommand) other;
+ return index.equals(e.index)
+ && editTemplateDescriptor.equals(e.editTemplateDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the template with. Each non-empty field value will replace the
+ * corresponding field value of the template.
+ */
+ public static class EditTemplateDescriptor {
+ private Subject subject;
+ private Body body;
+
+ public EditTemplateDescriptor() {}
+
+ /**
+ * Copy constructor.
+ */
+ public EditTemplateDescriptor(EditTemplateDescriptor toCopy) {
+ setSubject(toCopy.subject);
+ setBody(toCopy.body);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(subject, body);
+ }
+
+ public void setSubject(Subject subject) {
+ this.subject = subject;
+ }
+
+ public Optional getSubject() {
+ return Optional.ofNullable(subject);
+ }
+
+ public void setBody(Body body) {
+ this.body = body;
+ }
+
+ public Optional getBody() {
+ return Optional.ofNullable(body);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof EditTemplateDescriptor
+ && getSubject().equals(((EditTemplateDescriptor) other).getSubject())
+ && getBody().equals(((EditTemplateDescriptor) other).getBody()));
+ }
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/mails/FindTemplateCommand.java b/src/main/java/seedu/mycrm/logic/commands/mails/FindTemplateCommand.java
new file mode 100644
index 00000000000..ac9a3284c56
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/mails/FindTemplateCommand.java
@@ -0,0 +1,53 @@
+package seedu.mycrm.logic.commands.mails;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.mail.SubjectContainsKeywordsPredicate;
+
+/**
+ * Finds and lists all templates in myCrm whose subject contains any of the argument keywords.
+ * Keyword matching is case-insensitive.
+ */
+public class FindTemplateCommand extends Command {
+
+ public static final String COMMAND_WORD = "findTemplate";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all templates whose subjects 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 + " Completed";
+
+ private static final CommandType COMMAND_TYPE = CommandType.TEMPLATE;
+
+ private final SubjectContainsKeywordsPredicate predicate;
+
+ public FindTemplateCommand(SubjectContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ requireNonNull(model);
+ model.updateFilteredTemplateList(predicate);
+ return stateManager.handleList(new CommandResult(String.format(Messages.MESSAGE_TEMPLATES_LISTED_OVERVIEW,
+ model.getFilteredTemplateList().size()), COMMAND_TYPE));
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof FindTemplateCommand
+ && predicate.equals(((FindTemplateCommand) other).predicate));
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/mails/ListTemplateCommand.java b/src/main/java/seedu/mycrm/logic/commands/mails/ListTemplateCommand.java
new file mode 100644
index 00000000000..022fab59757
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/mails/ListTemplateCommand.java
@@ -0,0 +1,34 @@
+package seedu.mycrm.logic.commands.mails;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_TEMPLATES;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.model.Model;
+
+/**
+ * Lists all templates in the myCrm to the user.
+ */
+public class ListTemplateCommand extends Command {
+
+ public static final String COMMAND_WORD = "listTemplate";
+
+ public static final String MESSAGE_SUCCESS = "Listed all templates";
+
+ private static final CommandType COMMAND_TYPE = CommandType.TEMPLATE;
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ requireNonNull(model);
+ model.updateFilteredTemplateList(PREDICATE_SHOW_ALL_TEMPLATES);
+ return new CommandResult(MESSAGE_SUCCESS, COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/mails/MailCommand.java b/src/main/java/seedu/mycrm/logic/commands/mails/MailCommand.java
new file mode 100644
index 00000000000..04741ca28ed
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/mails/MailCommand.java
@@ -0,0 +1,91 @@
+package seedu.mycrm.logic.commands.mails;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_JOB_INDEX;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_TEMPLATE_INDEX;
+
+import seedu.mycrm.commons.core.Messages;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.mail.Mail;
+import seedu.mycrm.model.mail.Template;
+
+public class MailCommand extends Command {
+ public static final String COMMAND_WORD = "mail";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Constructs the mail with identified template to target job by their index number used in the "
+ + "displayed template and contact list.\n"
+ + "Parameters: "
+ + PREFIX_JOB_INDEX + "JOB INDEX "
+ + PREFIX_TEMPLATE_INDEX + "TEMPLATE INDEX\n"
+ + "Example: "
+ + COMMAND_WORD + " "
+ + PREFIX_JOB_INDEX + "1 "
+ + PREFIX_TEMPLATE_INDEX + "1 \n";
+
+ public static final String MESSAGE_MAIL_SUCCESS = "New Email Type '%s': \nMailto URL is ready";
+
+ private static final CommandType COMMAND_TYPE = CommandType.MAIL;
+
+ private final Index jobIndex;
+
+ private final Index templateIndex;
+
+ /**
+ * Creates a MailCommand to mail the specified {@code Job} and {@code Template}
+ */
+ public MailCommand(Index jobIndex, Index templateIndex) {
+ requireNonNull(jobIndex);
+ requireNonNull(templateIndex);
+ this.jobIndex = jobIndex;
+ this.templateIndex = templateIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+
+ java.util.List lastJobList = model.getFilteredJobList();
+ java.util.List lastTemplateList = model.getFilteredTemplateList();
+
+ if (jobIndex.getZeroBased() >= lastJobList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_JOB_DISPLAYED_INDEX);
+ }
+
+ if (templateIndex.getZeroBased() >= lastTemplateList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TEMPLATE_DISPLAYED_INDEX);
+ }
+
+ Job jobToMail = lastJobList.get(jobIndex.getZeroBased());
+
+ if (jobToMail.getClientEmail().equals("")) {
+ throw new CommandException(Messages.MESSAGE_INVALID_JOB_NO_EMAIL);
+ }
+
+ Template templateToMail = lastTemplateList.get(templateIndex.getZeroBased());
+ model.addMail(new Mail(jobToMail, templateToMail));
+
+ return new CommandResult(String.format(MESSAGE_MAIL_SUCCESS, templateToMail.getSubject().toString()),
+ COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof MailCommand
+ && jobIndex.equals(((MailCommand) other).jobIndex))
+ && templateIndex.equals(((MailCommand) other).templateIndex);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/products/AddProductCommand.java b/src/main/java/seedu/mycrm/logic/commands/products/AddProductCommand.java
new file mode 100644
index 00000000000..b8aa3e31548
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/products/AddProductCommand.java
@@ -0,0 +1,82 @@
+package seedu.mycrm.logic.commands.products;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.product.Product;
+
+/**
+ * Adds a product to MyCRM.
+ */
+public class AddProductCommand extends Command {
+
+ public static final String COMMAND_WORD = "addProduct";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Add a new product to the CRM."
+ + "\nParameters: n/NAME [t/TYPE] [m/MANUFACTURER] [d/DESCRIPTION]"
+ + "\nNote: Product name cannot be empty."
+ + "\nExample: addProduct n/Intel i5-10400F t/CPU m/Intel d/2.90GHz";
+
+ public static final String MESSAGE_SUCCESS = "New product added: %1$s\n";
+
+ public static final String MESSAGE_DUPLICATE_PRODUCT = "This product already exists in MyCRM";
+
+ private static final CommandType COMMAND_TYPE = CommandType.PRODUCTS;
+
+ private final Product toAdd;
+
+ /**
+ * Creates an AddProductCommand to add the specified {@code product}.
+ */
+ public AddProductCommand(Product product) {
+ requireNonNull(product);
+
+ this.toAdd = product;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+ requireNonNull(stateManager);
+
+ if (model.hasProduct(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PRODUCT);
+ }
+
+ model.addProduct(toAdd);
+ CommandResult commandResult = new CommandResult(String.format(MESSAGE_SUCCESS, toString()), COMMAND_TYPE);
+ return stateManager.handleProduct(toAdd, commandResult);
+ }
+
+ @Override
+ public String toString() {
+ return toAdd.toString();
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ if (o instanceof AddProductCommand) {
+ AddProductCommand cmd = (AddProductCommand) o;
+ return cmd.toAdd.equals(this.toAdd);
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/products/DeleteProductCommand.java b/src/main/java/seedu/mycrm/logic/commands/products/DeleteProductCommand.java
new file mode 100644
index 00000000000..17686f9c3b5
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/products/DeleteProductCommand.java
@@ -0,0 +1,93 @@
+package seedu.mycrm.logic.commands.products;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_REMOVE_LINKED_PRODUCT;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.product.Product;
+
+/** Deletes the specified product from the CRM. */
+public class DeleteProductCommand extends Command {
+
+ public static final String COMMAND_WORD = "deleteProduct";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the product identified by the index number used in the displayed product list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_PRODUCT_SUCCESS = "Deleted Product: %1$s";
+
+ private static final CommandType COMMAND_TYPE = CommandType.PRODUCTS;
+
+ private final Index targetIndex;
+
+ /**
+ * Creates a DeleteProductCommand.
+ */
+ public DeleteProductCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireNonNull(model);
+
+ List lastShownList = model.getFilteredProductList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX);
+ }
+
+ Product productToDelete = lastShownList.get(targetIndex.getZeroBased());
+
+ // check the full job list to find if the product is linked with any jobs
+ Predicate latestJobPredicate = model.getLatestJobPredicate() == null
+ ? model.PREDICATE_SHOW_ALL_INCOMPLETE_JOBS
+ : model.getLatestJobPredicate();
+ model.updateFilteredJobList(Model.PREDICATE_SHOW_ALL_JOBS);
+ boolean isLinkedToJob = model.getFilteredJobList().stream()
+ .anyMatch(j -> j.getProduct() != null && j.getProduct().isSameProduct(productToDelete));
+
+ // restore the user's job predicate
+ model.updateFilteredJobList(latestJobPredicate);
+
+ if (isLinkedToJob) {
+ throw new CommandException(MESSAGE_REMOVE_LINKED_PRODUCT);
+ }
+
+ model.deleteProduct(productToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_PRODUCT_SUCCESS, productToDelete), COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof DeleteProductCommand)) {
+ return false;
+ }
+
+ return this.targetIndex.equals(((DeleteProductCommand) o).targetIndex);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/products/EditProductCommand.java b/src/main/java/seedu/mycrm/logic/commands/products/EditProductCommand.java
new file mode 100644
index 00000000000..dcd943f213f
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/products/EditProductCommand.java
@@ -0,0 +1,228 @@
+package seedu.mycrm.logic.commands.products;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX;
+import static seedu.mycrm.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_DESCRIPTION;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_MANUFACTURER;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_NAME;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_TYPE;
+
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.logic.commands.exceptions.CommandException;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.product.Description;
+import seedu.mycrm.model.product.Manufacturer;
+import seedu.mycrm.model.product.Product;
+import seedu.mycrm.model.product.ProductComponent;
+import seedu.mycrm.model.product.ProductName;
+import seedu.mycrm.model.product.Type;
+
+/** Edits an existing product in the CRM. */
+public class EditProductCommand extends Command {
+
+ public static final String COMMAND_WORD = "editProduct";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the product identified "
+ + "by the index number used in the displayed product list. "
+ + "Index number must be a positive integer.\n"
+ + "Parameters: INDEX "
+ + "[" + PREFIX_PRODUCT_NAME + "NAME] "
+ + "[" + PREFIX_PRODUCT_TYPE + "TYPE] "
+ + "[" + PREFIX_PRODUCT_MANUFACTURER + "MANUFACTURER] "
+ + "[" + PREFIX_PRODUCT_DESCRIPTION + "DESCRIPTION]\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_PRODUCT_MANUFACTURER + "Gigabyte "
+ + PREFIX_PRODUCT_TYPE + "Motherboard";
+
+ public static final String MESSAGE_EDIT_PRODUCT_SUCCESS = "Edited Product: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_PRODUCT = "This product already exists in MyCRM.";
+
+ private static final CommandType COMMAND_TYPE = CommandType.PRODUCTS;
+
+ private final Index index;
+ private final EditProductDescriptor descriptor;
+
+ /**
+ * Creates an EditProductCommand.
+ */
+ public EditProductCommand(Index index, EditProductDescriptor descriptor) {
+ requireAllNonNull(index, descriptor);
+
+ this.index = index;
+ this.descriptor = descriptor;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) throws CommandException {
+ requireAllNonNull(model, stateManager);
+
+ if (index.getOneBased() > model.getFilteredProductList().size()) {
+ throw new CommandException(MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX);
+ }
+
+ Product toEdit = model.getFilteredProductList().get(index.getZeroBased());
+ Product edited = createEditedProduct(toEdit, descriptor);
+
+ boolean hasDuplicateProduct = !edited.isSameProduct(toEdit) && model.hasProduct(edited);
+ if (hasDuplicateProduct) {
+ throw new CommandException(MESSAGE_DUPLICATE_PRODUCT);
+ }
+
+ // update product in product list
+ model.setProduct(toEdit, edited);
+ model.updateFilteredProductList(Model.PREDICATE_SHOW_ALL_PRODUCTS);
+
+ // update product references in jobs
+ Predicate latestJobPredicate = model.getLatestJobPredicate() == null
+ ? model.PREDICATE_SHOW_ALL_INCOMPLETE_JOBS
+ : model.getLatestJobPredicate();
+
+ model.updateFilteredJobList(Model.PREDICATE_SHOW_ALL_JOBS);
+ model.getFilteredJobList().stream()
+ .filter(j -> j.getProduct() != null && j.getProduct().isSameProduct(toEdit))
+ .forEach(j -> {
+ j.setProduct(edited);
+ model.setJob(j, j);
+ });
+
+ // restore the user's job predicate
+ model.updateFilteredJobList(latestJobPredicate);
+
+ return new CommandResult(String.format(MESSAGE_EDIT_PRODUCT_SUCCESS, edited), COMMAND_TYPE);
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ private static Product createEditedProduct(Product toEdit, EditProductDescriptor descriptor) {
+ requireNonNull(toEdit);
+
+ ProductName name = descriptor.productName.isEmpty() ? toEdit.getName() : descriptor.productName;
+ Type type = descriptor.type.isEmpty() ? toEdit.getType() : descriptor.type;
+ Manufacturer manufacturer = descriptor.manufacturer.isEmpty() ? toEdit.getManufacturer()
+ : descriptor.manufacturer;
+ Description description = descriptor.description.isEmpty() ? toEdit.getDescription() : descriptor.description;
+
+ return new Product(name, type, manufacturer, description);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ if (o instanceof EditProductCommand) {
+ EditProductCommand cmd = (EditProductCommand) o;
+ return cmd.index.equals(this.index) && cmd.descriptor.equals(this.descriptor);
+ }
+ return false;
+ }
+
+ /**
+ * Stores the details to edit the product with. Each non-empty field value will replace the
+ * corresponding field value of the product.
+ */
+ public static class EditProductDescriptor {
+ private ProductName productName;
+ private Type type;
+ private Manufacturer manufacturer;
+ private Description description;
+
+ /**
+ * Creates an EditProductDescriptor with all fields initialized to empty values.
+ */
+ public EditProductDescriptor() {
+ productName = ProductName.getEmptyName();
+ type = Type.getEmptyType();
+ manufacturer = Manufacturer.getEmptyManufacturer();
+ description = Description.getEmptyDescription();
+ }
+
+ /**
+ * Creates an EditProductDescriptor with the same fields as {@code toCopy}.
+ */
+ public EditProductDescriptor(EditProductDescriptor toCopy) {
+ setProductName(toCopy.productName);
+ setType(toCopy.type);
+ setManufacturer(toCopy.manufacturer);
+ setDescription(toCopy.description);
+ }
+
+ /** Returns true if at least one field is edited. */
+ public boolean isAnyFieldEdited() {
+ return isAnyNonEmpty(productName, type, manufacturer, description);
+ }
+
+ private boolean isAnyNonEmpty(ProductComponent... components) {
+ return Arrays.stream(components).anyMatch(component -> !component.isEmpty());
+ }
+
+ public void setProductName(ProductName name) {
+ this.productName = name;
+ }
+
+ public ProductName getName() {
+ return this.productName;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+ public Type getType() {
+ return this.type;
+ }
+
+ public void setManufacturer(Manufacturer manufacturer) {
+ this.manufacturer = manufacturer;
+ }
+
+ public Manufacturer getManufacturer() {
+ return this.manufacturer;
+ }
+
+ public void setDescription(Description description) {
+ this.description = description;
+ }
+
+ public Description getDescription() {
+ return this.description;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (o == null) {
+ return false;
+ }
+ if (o instanceof EditProductDescriptor) {
+ EditProductDescriptor descriptor = (EditProductDescriptor) o;
+ return descriptor.productName.equals(this.getName())
+ && descriptor.type.equals(this.getType())
+ && descriptor.manufacturer.equals(this.getManufacturer())
+ && descriptor.description.equals(this.getDescription());
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/products/FindProductCommand.java b/src/main/java/seedu/mycrm/logic/commands/products/FindProductCommand.java
new file mode 100644
index 00000000000..2a85463d450
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/products/FindProductCommand.java
@@ -0,0 +1,58 @@
+package seedu.mycrm.logic.commands.products;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_PRODUCTS_LISTED_OVERVIEW;
+import static seedu.mycrm.commons.util.CollectionUtil.requireAllNonNull;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.model.Model;
+import seedu.mycrm.model.product.ProductNameContainsKeywordsPredicate;
+
+/**
+ * Finds and lists all products in MyCrm whose name contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class FindProductCommand extends Command {
+ public static final String COMMAND_WORD = "findProduct";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all products 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 + "Intel";
+
+ private static final CommandType COMMAND_TYPE = CommandType.PRODUCTS;
+
+ private final ProductNameContainsKeywordsPredicate predicate;
+
+ /** Creates a FindProductCommand. */
+ public FindProductCommand(ProductNameContainsKeywordsPredicate predicate) {
+ requireNonNull(predicate);
+
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ requireAllNonNull(model, stateManager);
+
+ model.updateFilteredProductList(predicate);
+
+ return stateManager.handleList(new CommandResult(String.format(MESSAGE_PRODUCTS_LISTED_OVERVIEW,
+ model.getFilteredProductList().size()), COMMAND_TYPE));
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindProductCommand // instanceof handles nulls
+ && predicate.equals(((FindProductCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/commands/products/ListProductCommand.java b/src/main/java/seedu/mycrm/logic/commands/products/ListProductCommand.java
new file mode 100644
index 00000000000..36043e0ef14
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/commands/products/ListProductCommand.java
@@ -0,0 +1,36 @@
+package seedu.mycrm.logic.commands.products;
+
+import static seedu.mycrm.commons.util.CollectionUtil.requireAllNonNull;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_PRODUCTS;
+
+import seedu.mycrm.logic.StateManager;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.CommandResult;
+import seedu.mycrm.logic.commands.CommandType;
+import seedu.mycrm.model.Model;
+
+/**
+ * Lists all products in MyCrm to the user.
+ */
+public class ListProductCommand extends Command {
+
+ public static final String COMMAND_WORD = "listProduct";
+
+ public static final String MESSAGE_SUCCESS = "Listed all products";
+
+ private static final CommandType COMMAND_TYPE = CommandType.PRODUCTS;
+
+ @Override
+ public CommandResult execute(Model model, StateManager stateManager) {
+ requireAllNonNull(model, stateManager);
+
+ model.updateFilteredProductList(PREDICATE_SHOW_ALL_PRODUCTS);
+
+ return stateManager.handleList(new CommandResult(MESSAGE_SUCCESS, COMMAND_TYPE));
+ }
+
+ @Override
+ public CommandType getType() {
+ return COMMAND_TYPE;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/mycrm/logic/parser/ArgumentMultimap.java
similarity index 98%
rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
rename to src/main/java/seedu/mycrm/logic/parser/ArgumentMultimap.java
index 954c8e18f8e..20200afd948 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
+++ b/src/main/java/seedu/mycrm/logic/parser/ArgumentMultimap.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package seedu.mycrm.logic.parser;
import java.util.ArrayList;
import java.util.HashMap;
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/mycrm/logic/parser/ArgumentTokenizer.java
similarity index 93%
rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
rename to src/main/java/seedu/mycrm/logic/parser/ArgumentTokenizer.java
index 5c9aebfa488..cf39475af8e 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
+++ b/src/main/java/seedu/mycrm/logic/parser/ArgumentTokenizer.java
@@ -1,9 +1,10 @@
-package seedu.address.logic.parser;
+package seedu.mycrm.logic.parser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Tokenizes arguments string of the form: {@code preamble value value ...}
@@ -145,4 +146,11 @@ Prefix getPrefix() {
}
}
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ public static boolean arePrefixesNotPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return !Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
}
diff --git a/src/main/java/seedu/mycrm/logic/parser/CliSyntax.java b/src/main/java/seedu/mycrm/logic/parser/CliSyntax.java
new file mode 100644
index 00000000000..7ab929bf4df
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/CliSyntax.java
@@ -0,0 +1,35 @@
+package seedu.mycrm.logic.parser;
+
+/**
+ * Contains Command Line Interface (CLI) syntax definitions common to multiple commands
+ */
+public class CliSyntax {
+
+ /* Prefix definitions for Contacts */
+ public static final Prefix PREFIX_NAME = new Prefix("n/");
+ public static final Prefix PREFIX_PHONE = new Prefix("c/");
+ 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/");
+
+ /* Prefix definitions for Emails */
+ public static final Prefix PREFIX_SUBJECT = new Prefix("s/");
+ public static final Prefix PREFIX_BODY = new Prefix("b/");
+ public static final Prefix PREFIX_JOB_INDEX = new Prefix("j/");
+ public static final Prefix PREFIX_TEMPLATE_INDEX = new Prefix("t/");
+
+
+ /* Prefix definitions for Jobs */
+ public static final Prefix PREFIX_JOB_DESCRIPTION = new Prefix("d/");
+ public static final Prefix PREFIX_CONTACT_INDEX = new Prefix("c/");
+ public static final Prefix PREFIX_PRODUCT_INDEX = new Prefix("p/");
+ public static final Prefix PREFIX_EXPECTED_COMPLETION_DATE = new Prefix("by/");
+ public static final Prefix PREFIX_RECEIVED_DATE = new Prefix("recv/");
+ public static final Prefix PREFIX_FEE = new Prefix("fee/");
+
+ /* Prefix definitions for products*/
+ public static final Prefix PREFIX_PRODUCT_NAME = new Prefix("n/");
+ public static final Prefix PREFIX_PRODUCT_TYPE = new Prefix("t/");
+ public static final Prefix PREFIX_PRODUCT_MANUFACTURER = new Prefix("m/");
+ public static final Prefix PREFIX_PRODUCT_DESCRIPTION = new Prefix("d/");
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/MyCrmParser.java b/src/main/java/seedu/mycrm/logic/parser/MyCrmParser.java
new file mode 100644
index 00000000000..e639015e35a
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/MyCrmParser.java
@@ -0,0 +1,232 @@
+package seedu.mycrm.logic.parser;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import seedu.mycrm.logic.commands.AbortCommand;
+import seedu.mycrm.logic.commands.ClearCommand;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.commands.ExitCommand;
+import seedu.mycrm.logic.commands.ExportReportCommand;
+import seedu.mycrm.logic.commands.HelpCommand;
+import seedu.mycrm.logic.commands.PrintReportCommand;
+import seedu.mycrm.logic.commands.SelectCommand;
+import seedu.mycrm.logic.commands.ThemeCommand;
+import seedu.mycrm.logic.commands.contacts.AddContactCommand;
+import seedu.mycrm.logic.commands.contacts.DeleteContactCommand;
+import seedu.mycrm.logic.commands.contacts.EditContactCommand;
+import seedu.mycrm.logic.commands.contacts.FindContactCommand;
+import seedu.mycrm.logic.commands.contacts.HideContactCommand;
+import seedu.mycrm.logic.commands.contacts.ListContactCommand;
+import seedu.mycrm.logic.commands.contacts.UndoHideContactCommand;
+import seedu.mycrm.logic.commands.history.ClearHistoryCommand;
+import seedu.mycrm.logic.commands.history.HistoryCommand;
+import seedu.mycrm.logic.commands.jobs.AddJobCommand;
+import seedu.mycrm.logic.commands.jobs.CompleteJobCommand;
+import seedu.mycrm.logic.commands.jobs.DeleteJobCommand;
+import seedu.mycrm.logic.commands.jobs.EditJobCommand;
+import seedu.mycrm.logic.commands.jobs.FindJobCommand;
+import seedu.mycrm.logic.commands.jobs.ListJobCommand;
+import seedu.mycrm.logic.commands.jobs.UndoCompleteJobCommand;
+import seedu.mycrm.logic.commands.mails.AddTemplateCommand;
+import seedu.mycrm.logic.commands.mails.DeleteTemplateCommand;
+import seedu.mycrm.logic.commands.mails.EditTemplateCommand;
+import seedu.mycrm.logic.commands.mails.FindTemplateCommand;
+import seedu.mycrm.logic.commands.mails.ListTemplateCommand;
+import seedu.mycrm.logic.commands.mails.MailCommand;
+import seedu.mycrm.logic.commands.products.AddProductCommand;
+import seedu.mycrm.logic.commands.products.DeleteProductCommand;
+import seedu.mycrm.logic.commands.products.EditProductCommand;
+import seedu.mycrm.logic.commands.products.FindProductCommand;
+import seedu.mycrm.logic.commands.products.ListProductCommand;
+import seedu.mycrm.logic.parser.contacts.AddContactCommandParser;
+import seedu.mycrm.logic.parser.contacts.DeleteContactCommandParser;
+import seedu.mycrm.logic.parser.contacts.EditContactCommandParser;
+import seedu.mycrm.logic.parser.contacts.FindContactCommandParser;
+import seedu.mycrm.logic.parser.contacts.HideContactCommandParser;
+import seedu.mycrm.logic.parser.contacts.ListContactCommandParser;
+import seedu.mycrm.logic.parser.contacts.UndoHideContactCommandParser;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.logic.parser.jobs.AddJobCommandParser;
+import seedu.mycrm.logic.parser.jobs.CompleteJobCommandParser;
+import seedu.mycrm.logic.parser.jobs.DeleteJobCommandParser;
+import seedu.mycrm.logic.parser.jobs.EditJobCommandParser;
+import seedu.mycrm.logic.parser.jobs.FindJobCommandParser;
+import seedu.mycrm.logic.parser.jobs.ListJobCommandParser;
+import seedu.mycrm.logic.parser.jobs.UndoCompleteJobCommandParser;
+import seedu.mycrm.logic.parser.mails.AddTemplateCommandParser;
+import seedu.mycrm.logic.parser.mails.DeleteTemplateCommandParser;
+import seedu.mycrm.logic.parser.mails.EditTemplateCommandParser;
+import seedu.mycrm.logic.parser.mails.FindTemplateCommandParser;
+import seedu.mycrm.logic.parser.mails.MailCommandParser;
+import seedu.mycrm.logic.parser.products.AddProductCommandParser;
+import seedu.mycrm.logic.parser.products.DeleteProductCommandParser;
+import seedu.mycrm.logic.parser.products.EditProductCommandParser;
+import seedu.mycrm.logic.parser.products.FindProductCommandParser;
+
+/**
+ * Parses user input.
+ */
+public class MyCrmParser {
+
+ /**
+ * Used for initial separation of command word and args.
+ */
+ private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)");
+
+ /**
+ * Used to parse the command word from the user input
+ *
+ * @param userInput full user input string
+ * @return the command word
+ * @throws ParseException if the user input does not conform to the expected format
+ */
+ public static String parseCommandWord(String userInput) throws ParseException {
+ Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
+ if (!matcher.matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
+ }
+ return matcher.group("commandWord");
+ }
+
+ /**
+ * Used to parse the arguments from the user input
+ * @param userInput full user input string
+ * @return the arguments of the command
+ * @throws ParseException if the user input does not conform to the expected format
+ */
+ public static String parseArguments(String userInput) throws ParseException {
+ Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
+ if (!matcher.matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
+ }
+ return matcher.group("arguments");
+ }
+
+ /**
+ * Parses user input into command for execution.
+ *
+ * @param userInput full user input string
+ * @return the command based on the user input
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public Command parseCommand(String userInput) throws ParseException {
+ final String commandWord = parseCommandWord(userInput);
+ final String arguments = parseArguments(userInput);
+
+ switch (commandWord) {
+
+ case AddContactCommand.COMMAND_WORD:
+ return new AddContactCommandParser().parse(arguments);
+
+ case EditContactCommand.COMMAND_WORD:
+ return new EditContactCommandParser().parse(arguments);
+
+ case DeleteContactCommand.COMMAND_WORD:
+ return new DeleteContactCommandParser().parse(arguments);
+
+ case FindContactCommand.COMMAND_WORD:
+ return new FindContactCommandParser().parse(arguments);
+
+ case HideContactCommand.COMMAND_WORD:
+ return new HideContactCommandParser().parse(arguments);
+
+ case UndoHideContactCommand.COMMAND_WORD:
+ return new UndoHideContactCommandParser().parse(arguments);
+
+ case ListContactCommand.COMMAND_WORD:
+ return new ListContactCommandParser().parse(arguments);
+
+ case ClearCommand.COMMAND_WORD:
+ return new ClearCommand();
+
+ case ExitCommand.COMMAND_WORD:
+ return new ExitCommand();
+
+ case HelpCommand.COMMAND_WORD:
+ return new HelpCommand();
+
+ case ThemeCommand.COMMAND_WORD:
+ return new ThemeCommandParser().parse(arguments);
+
+ case AddProductCommand.COMMAND_WORD:
+ return new AddProductCommandParser().parse(arguments);
+
+ case ListProductCommand.COMMAND_WORD:
+ return new ListProductCommand();
+
+ case EditProductCommand.COMMAND_WORD:
+ return new EditProductCommandParser().parse(arguments);
+
+ case FindProductCommand.COMMAND_WORD:
+ return new FindProductCommandParser().parse(arguments);
+
+ case DeleteProductCommand.COMMAND_WORD:
+ return new DeleteProductCommandParser().parse(arguments);
+
+ case AddTemplateCommand.COMMAND_WORD:
+ return new AddTemplateCommandParser().parse(arguments);
+
+ case ListTemplateCommand.COMMAND_WORD:
+ return new ListTemplateCommand();
+
+ case EditTemplateCommand.COMMAND_WORD:
+ return new EditTemplateCommandParser().parse(arguments);
+
+ case DeleteTemplateCommand.COMMAND_WORD:
+ return new DeleteTemplateCommandParser().parse(arguments);
+
+ case FindTemplateCommand.COMMAND_WORD:
+ return new FindTemplateCommandParser().parse(arguments);
+
+ case MailCommand.COMMAND_WORD:
+ return new MailCommandParser().parse(arguments);
+
+ case AddJobCommand.COMMAND_WORD:
+ return new AddJobCommandParser().parse(arguments);
+
+ case FindJobCommand.COMMAND_WORD:
+ return new FindJobCommandParser().parse(arguments);
+
+ case ListJobCommand.COMMAND_WORD:
+ return new ListJobCommandParser().parse(arguments);
+
+ case CompleteJobCommand.COMMAND_WORD:
+ return new CompleteJobCommandParser().parse(arguments);
+
+ case UndoCompleteJobCommand.COMMAND_WORD:
+ return new UndoCompleteJobCommandParser().parse(arguments);
+
+ case EditJobCommand.COMMAND_WORD:
+ return new EditJobCommandParser().parse(arguments);
+
+ case DeleteJobCommand.COMMAND_WORD:
+ return new DeleteJobCommandParser().parse(arguments);
+
+ case HistoryCommand.COMMAND_WORD:
+ return new HistoryCommand();
+
+ case ClearHistoryCommand.COMMAND_WORD:
+ return new ClearHistoryCommand();
+
+ case PrintReportCommand.COMMAND_WORD:
+ return new PrintReportCommandParser().parse(arguments);
+
+ case ExportReportCommand.COMMAND_WORD:
+ return new ExportReportCommand();
+
+ case SelectCommand.COMMAND_WORD:
+ return new SelectCommandParser().parse(arguments);
+
+ case AbortCommand.COMMAND_WORD:
+ return new AbortCommand();
+
+ default:
+ throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/mycrm/logic/parser/Parser.java
similarity index 72%
rename from src/main/java/seedu/address/logic/parser/Parser.java
rename to src/main/java/seedu/mycrm/logic/parser/Parser.java
index d6551ad8e3f..ca215745d25 100644
--- a/src/main/java/seedu/address/logic/parser/Parser.java
+++ b/src/main/java/seedu/mycrm/logic/parser/Parser.java
@@ -1,7 +1,7 @@
-package seedu.address.logic.parser;
+package seedu.mycrm.logic.parser;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.mycrm.logic.commands.Command;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
/**
* Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}.
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/mycrm/logic/parser/ParserUtil.java
similarity index 52%
rename from src/main/java/seedu/address/logic/parser/ParserUtil.java
rename to src/main/java/seedu/mycrm/logic/parser/ParserUtil.java
index b117acb9c55..bd641d9cf1f 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/mycrm/logic/parser/ParserUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package seedu.mycrm.logic.parser;
import static java.util.Objects.requireNonNull;
@@ -6,14 +6,20 @@
import java.util.HashSet;
import java.util.Set;
-import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.commons.util.StringUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.contact.Address;
+import seedu.mycrm.model.contact.Email;
+import seedu.mycrm.model.contact.Name;
+import seedu.mycrm.model.contact.Phone;
+import seedu.mycrm.model.contact.tag.Tag;
+import seedu.mycrm.model.job.JobDate;
+import seedu.mycrm.model.job.JobDescription;
+import seedu.mycrm.model.job.JobFee;
+import seedu.mycrm.model.mail.Body;
+import seedu.mycrm.model.mail.Subject;
+
/**
* Contains utility methods used for parsing strings in the various *Parser classes.
@@ -121,4 +127,85 @@ public static Set parseTags(Collection tags) throws ParseException
}
return tagSet;
}
+
+ /**
+ * Parses a {@code String subject} into an {@code Subject}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code subject} is invalid.
+ */
+ public static Subject parseSubject(String subject) throws ParseException {
+ requireNonNull(subject);
+ String trimmedSubject = subject.trim();
+ if (!Subject.isValidSubject(trimmedSubject)) {
+ throw new ParseException(Subject.MESSAGE_CONSTRAINTS);
+ }
+ return new Subject(trimmedSubject);
+ }
+
+ /**
+ * Parses a {@code String body} into an {@code Body}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code body} is invalid.
+ */
+ public static Body parseBody(String body) throws ParseException {
+ requireNonNull(body);
+ String trimmedBody = body.trim();
+ if (!Body.isValidBody(trimmedBody)) {
+ throw new ParseException(Body.MESSAGE_CONSTRAINTS);
+ }
+ return new Body(trimmedBody);
+ }
+
+ /**
+ * Parses a {@code String jobDescription} into an {@code JobDescription}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code jobDescription} is invalid.
+ */
+ public static JobDescription parseJobDescription(String jobDescription) throws ParseException {
+ requireNonNull(jobDescription);
+ String trimmedJobDescription = jobDescription.trim();
+ if (!JobDescription.isValidJobDescription(trimmedJobDescription)) {
+ throw new ParseException(JobDescription.MESSAGE_CONSTRAINTS);
+ }
+ return new JobDescription(trimmedJobDescription);
+ }
+
+ /**
+ * Parses a {@code String date} into an {@code JobDate}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code date} is invalid.
+ */
+ public static JobDate parseJobDate(String date, String attributeName) throws ParseException {
+ requireNonNull(date);
+ String trimmedDate = date.trim();
+ if (!JobDate.isValidJobDate(date)) {
+ throw new ParseException(attributeName + " " + JobDate.MESSAGE_CONSTRAINTS);
+ }
+ return new JobDate(trimmedDate);
+ }
+
+ /**
+ * Parses a {@code String date} into an {@code JobDate}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code date} is invalid.
+ */
+ public static JobFee parseJobFee(String fee) throws ParseException {
+ requireNonNull(fee);
+ if (!JobFee.isValidJobFee(fee)) {
+ throw new ParseException(JobFee.MESSAGE_CONSTRAINTS);
+ }
+ return new JobFee(fee);
+ }
+
+ /**
+ * Parses {@code string} and returns it. Leading and trailing whitespaces will be trimmed.
+ */
+ public static String parseString(String line) throws ParseException {
+ return line.trim();
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/mycrm/logic/parser/Prefix.java
similarity index 95%
rename from src/main/java/seedu/address/logic/parser/Prefix.java
rename to src/main/java/seedu/mycrm/logic/parser/Prefix.java
index c859d5fa5db..91184637e29 100644
--- a/src/main/java/seedu/address/logic/parser/Prefix.java
+++ b/src/main/java/seedu/mycrm/logic/parser/Prefix.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.parser;
+package seedu.mycrm.logic.parser;
/**
* A prefix that marks the beginning of an argument in an arguments string.
diff --git a/src/main/java/seedu/mycrm/logic/parser/PrintReportCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/PrintReportCommandParser.java
new file mode 100644
index 00000000000..1045bc56af5
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/PrintReportCommandParser.java
@@ -0,0 +1,44 @@
+package seedu.mycrm.logic.parser;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.logic.commands.PrintReportCommand.SHOW_IN_PROGRESS_FLAG;
+import static seedu.mycrm.logic.commands.PrintReportCommand.SHOW_PRODUCT_FLAG;
+
+import seedu.mycrm.logic.commands.PrintReportCommand;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+public class PrintReportCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the PrintReportCommand
+ * and returns an PrintReportCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public PrintReportCommand parse(String args) throws ParseException {
+ String flag = null;
+ String trimmedArgs = args.trim();
+
+ if (trimmedArgs.isEmpty()) {
+ return new PrintReportCommand();
+ }
+
+ String[] nameKeywords = trimmedArgs.split("\\s+");
+
+ if (nameKeywords.length > 1) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ PrintReportCommand.MESSAGE_USAGE));
+ } else if (nameKeywords.length == 1) {
+ if (SHOW_IN_PROGRESS_FLAG.equals(nameKeywords[0])) {
+ flag = SHOW_IN_PROGRESS_FLAG;
+ } else if (SHOW_PRODUCT_FLAG.equals(nameKeywords[0])) {
+ flag = SHOW_PRODUCT_FLAG;
+ } else {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ PrintReportCommand.MESSAGE_USAGE));
+ }
+ }
+ return new PrintReportCommand(flag);
+ }
+
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/SelectCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/SelectCommandParser.java
new file mode 100644
index 00000000000..27ddde67d20
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/SelectCommandParser.java
@@ -0,0 +1,24 @@
+package seedu.mycrm.logic.parser;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.SelectCommand;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+public class SelectCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the SelectCommand
+ * and returns a SelectCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public SelectCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new SelectCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/ThemeCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/ThemeCommandParser.java
new file mode 100644
index 00000000000..e89cb2b0ce6
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/ThemeCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.mycrm.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.logic.commands.ThemeCommand.MESSAGE_THEME_NOT_EXIST;
+import static seedu.mycrm.ui.ThemeManager.hasTheme;
+
+import seedu.mycrm.logic.commands.ThemeCommand;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+/** Parses input arguments and creates a ThemeCommand object. */
+public class ThemeCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the {@code ThemeCommand}
+ * and returns a {@code ThemeCommand} object for execution.
+ *
+ * @throws ParseException if the theme name provided by user does not exist in MyCrm.
+ */
+ @Override
+ public ThemeCommand parse(String userInput) throws ParseException {
+ requireNonNull(userInput);
+
+ String themeName = userInput.strip().toLowerCase();
+ if (!hasTheme(themeName)) {
+ throw new ParseException(MESSAGE_THEME_NOT_EXIST);
+ }
+
+ return new ThemeCommand(themeName);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/contacts/AddContactCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/contacts/AddContactCommandParser.java
new file mode 100644
index 00000000000..d55d1771234
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/contacts/AddContactCommandParser.java
@@ -0,0 +1,89 @@
+package seedu.mycrm.logic.parser.contacts;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.mycrm.logic.commands.contacts.AddContactCommand;
+import seedu.mycrm.logic.parser.ArgumentMultimap;
+import seedu.mycrm.logic.parser.ArgumentTokenizer;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.contact.Address;
+import seedu.mycrm.model.contact.Contact;
+import seedu.mycrm.model.contact.Email;
+import seedu.mycrm.model.contact.Name;
+import seedu.mycrm.model.contact.Phone;
+import seedu.mycrm.model.contact.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new AddContactCommand object
+ */
+public class AddContactCommandParser implements Parser {
+ private static final String EMPTY_PREFIX = "EMPTY";
+ /**
+ * Parses the given {@code String} of arguments in the context of the {@code AddContactCommand}
+ * and returns a {@code AddContactCommand} object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public AddContactCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_ADDRESS, PREFIX_TAG);
+
+ if (!argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddContactCommand.MESSAGE_USAGE));
+ }
+
+ Optional nameWrapper = argMultimap.getValue(PREFIX_NAME);
+ Name name;
+ if (nameWrapper.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddContactCommand.MESSAGE_USAGE));
+ } else if (nameWrapper.get().length() == 0) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddContactCommand.MESSAGE_USAGE));
+ } else {
+ name = Name.getName(nameWrapper.get());
+ }
+
+ Optional phoneWrapper = argMultimap.getValue(PREFIX_PHONE);
+ Phone phone;
+ phone = phoneWrapper.orElse(EMPTY_PREFIX).equals(EMPTY_PREFIX)
+ ? Phone.getEmptyPhone()
+ : Phone.getPhone(phoneWrapper.get());
+
+ Optional emailWrapper = argMultimap.getValue(PREFIX_EMAIL);
+ Email email = emailWrapper.orElse(EMPTY_PREFIX).equals(EMPTY_PREFIX)
+ ? Email.getEmptyEmail()
+ : Email.getEmail(emailWrapper.get());
+
+
+ Optional addressWrapper = argMultimap.getValue(PREFIX_ADDRESS);
+ Address address = addressWrapper.orElse(EMPTY_PREFIX).equals(EMPTY_PREFIX)
+ ? Address.getEmptyAddress()
+ : Address.getAddress(addressWrapper.get());
+
+ if (phone.isEmpty() && email.isEmpty() && address.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ AddContactCommand.MESSAGE_AT_LEAST_ONE_COMPONENT));
+ }
+
+ Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
+
+ Contact contact = new Contact(name, phone, email, address, tagList);
+
+ return new AddContactCommand(contact);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/contacts/DeleteContactCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/contacts/DeleteContactCommandParser.java
new file mode 100644
index 00000000000..8c1241f39f3
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/contacts/DeleteContactCommandParser.java
@@ -0,0 +1,31 @@
+package seedu.mycrm.logic.parser.contacts;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.contacts.DeleteContactCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteContactCommand object
+ */
+public class DeleteContactCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteContactCommand
+ * and returns a DeleteContactCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteContactCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteContactCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteContactCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/contacts/EditContactCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/contacts/EditContactCommandParser.java
new file mode 100644
index 00000000000..f2c7d6e458a
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/contacts/EditContactCommandParser.java
@@ -0,0 +1,85 @@
+package seedu.mycrm.logic.parser.contacts;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.contacts.EditContactCommand;
+import seedu.mycrm.logic.parser.ArgumentMultimap;
+import seedu.mycrm.logic.parser.ArgumentTokenizer;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.contact.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new EditContactCommand object
+ */
+public class EditContactCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditContactCommand
+ * and returns an EditContactCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditContactCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditContactCommand.MESSAGE_USAGE), pe);
+ }
+
+ EditContactCommand.EditContactDescriptor editContactDescriptor = new EditContactCommand.EditContactDescriptor();
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ editContactDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
+ editContactDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) {
+ editContactDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()));
+ }
+ if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
+ editContactDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
+ }
+ parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editContactDescriptor::setTags);
+
+ if (!editContactDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditContactCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditContactCommand(index, editContactDescriptor);
+ }
+
+ /**
+ * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty.
+ * If {@code tags} contain only one element which is an empty string, it will be parsed into a
+ * {@code Set} containing zero tags.
+ */
+ private Optional> parseTagsForEdit(Collection tags) throws ParseException {
+ assert tags != null;
+
+ if (tags.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
+ return Optional.of(ParserUtil.parseTags(tagSet));
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/contacts/FindContactCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/contacts/FindContactCommandParser.java
new file mode 100644
index 00000000000..cf4bb354787
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/contacts/FindContactCommandParser.java
@@ -0,0 +1,37 @@
+package seedu.mycrm.logic.parser.contacts;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import seedu.mycrm.logic.commands.contacts.FindContactCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.contact.NameContainsKeywordsPredicate;
+
+/**
+ * Parses input arguments and creates a new FindContactCommand object
+ */
+public class FindContactCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindContactCommand
+ * and returns a FindContactCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindContactCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindContactCommand.MESSAGE_USAGE));
+ }
+
+ String[] nameKeywords = trimmedArgs.split("\\s+");
+
+ NameContainsKeywordsPredicate keywordsPredicate = new NameContainsKeywordsPredicate(Arrays
+ .asList(nameKeywords));
+
+ return new FindContactCommand(keywordsPredicate);
+ }
+
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/contacts/HideContactCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/contacts/HideContactCommandParser.java
new file mode 100644
index 00000000000..29c3fa1b75b
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/contacts/HideContactCommandParser.java
@@ -0,0 +1,31 @@
+package seedu.mycrm.logic.parser.contacts;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.contacts.HideContactCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new HideContactCommand object
+ */
+public class HideContactCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the HideContactCommand
+ * and returns an HideContactCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public HideContactCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new HideContactCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, HideContactCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/contacts/ListContactCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/contacts/ListContactCommandParser.java
new file mode 100644
index 00000000000..83e650b3c08
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/contacts/ListContactCommandParser.java
@@ -0,0 +1,43 @@
+package seedu.mycrm.logic.parser.contacts;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.logic.commands.contacts.ListContactCommand.SHOW_ALL_CONTACTS;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_CONTACTS;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_NOT_HIDDEN_CONTACTS;
+
+import java.util.function.Predicate;
+
+import seedu.mycrm.logic.commands.contacts.ListContactCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.contact.Contact;
+
+/**
+ * Parses input arguments and creates a new ListContactCommand object
+ */
+public class ListContactCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ListContactCommand
+ * and returns an ListContactCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public ListContactCommand parse(String args) throws ParseException {
+ Predicate listPredicate;
+ String trimmedArgs = args.trim();
+
+ if (trimmedArgs.isEmpty()) {
+ listPredicate = PREDICATE_SHOW_NOT_HIDDEN_CONTACTS;
+ } else {
+ String[] nameKeywords = trimmedArgs.split("\\s+");
+ if (nameKeywords.length > 1 || !nameKeywords[0].equals(SHOW_ALL_CONTACTS)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ ListContactCommand.MESSAGE_USAGE));
+ }
+ listPredicate = PREDICATE_SHOW_ALL_CONTACTS;
+ }
+
+ return new ListContactCommand(listPredicate);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/contacts/UndoHideContactCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/contacts/UndoHideContactCommandParser.java
new file mode 100644
index 00000000000..a850ea7865d
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/contacts/UndoHideContactCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.mycrm.logic.parser.contacts;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.contacts.UndoHideContactCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new UndoHideContactCommand object
+ */
+public class UndoHideContactCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the UndoHideContactCommand
+ * and returns an UndoHideContactCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public UndoHideContactCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new UndoHideContactCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, UndoHideContactCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/seedu/mycrm/logic/parser/exceptions/ParseException.java
similarity index 73%
rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java
rename to src/main/java/seedu/mycrm/logic/parser/exceptions/ParseException.java
index 158a1a54c1c..eaf661aafcc 100644
--- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java
+++ b/src/main/java/seedu/mycrm/logic/parser/exceptions/ParseException.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.parser.exceptions;
+package seedu.mycrm.logic.parser.exceptions;
-import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.mycrm.commons.exceptions.IllegalValueException;
/**
* Represents a parse error encountered by a parser.
diff --git a/src/main/java/seedu/mycrm/logic/parser/jobs/AddJobCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/jobs/AddJobCommandParser.java
new file mode 100644
index 00000000000..0163feb47a1
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/jobs/AddJobCommandParser.java
@@ -0,0 +1,95 @@
+package seedu.mycrm.logic.parser.jobs;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_CONTACT_INDEX;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_EXPECTED_COMPLETION_DATE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_FEE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_JOB_DESCRIPTION;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_INDEX;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_RECEIVED_DATE;
+
+import java.util.stream.Stream;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.jobs.AddJobCommand;
+import seedu.mycrm.logic.parser.ArgumentMultimap;
+import seedu.mycrm.logic.parser.ArgumentTokenizer;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.Prefix;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.job.JobDate;
+import seedu.mycrm.model.job.JobDescription;
+import seedu.mycrm.model.job.JobFee;
+
+public class AddJobCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the {@code AddJobCommand}
+ * and returns a {@code AddJobCommand} object for execution.
+ *
+ * @throws ParseException if the user input does not conform to the expected format
+ */
+ @Override
+ public AddJobCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ Prefix[] mandatoryPrefixes = { PREFIX_JOB_DESCRIPTION, PREFIX_FEE, PREFIX_EXPECTED_COMPLETION_DATE};
+ Prefix[] allPrefixes = { PREFIX_JOB_DESCRIPTION, PREFIX_FEE, PREFIX_EXPECTED_COMPLETION_DATE,
+ PREFIX_CONTACT_INDEX, PREFIX_PRODUCT_INDEX, PREFIX_RECEIVED_DATE };
+
+ ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, allPrefixes);
+ validatePresenceOfMandatoryPrefixes(argumentMultimap, mandatoryPrefixes);
+ Job job = parsePrefixesToCreateJob(argumentMultimap);
+ Index contactIndex = parseIndexIfPresent(argumentMultimap, PREFIX_CONTACT_INDEX);
+ Index productIndex = parseIndexIfPresent(argumentMultimap, PREFIX_PRODUCT_INDEX);
+
+ return new AddJobCommand(job, contactIndex, productIndex);
+ }
+
+
+ private void validatePresenceOfMandatoryPrefixes(ArgumentMultimap argMultimap,
+ Prefix[] mandatoryPrefixes) throws ParseException {
+
+ if (!arePrefixesPresent(argMultimap, mandatoryPrefixes) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddJobCommand.MESSAGE_USAGE));
+ }
+ }
+
+
+ private Job parsePrefixesToCreateJob(ArgumentMultimap argMultimap) throws ParseException {
+ JobDescription jobDescription = ParserUtil.parseJobDescription(
+ argMultimap.getValue(PREFIX_JOB_DESCRIPTION).get());
+
+ JobDate expectedCompletionDate = ParserUtil.parseJobDate(
+ argMultimap.getValue(PREFIX_EXPECTED_COMPLETION_DATE).get(), "Expected Completion");
+
+ JobFee fee = ParserUtil.parseJobFee(
+ argMultimap.getValue(PREFIX_FEE).get());
+
+ JobDate receivedDate = (argMultimap.getValue(PREFIX_RECEIVED_DATE).isPresent())
+ ? ParserUtil.parseJobDate(argMultimap.getValue(PREFIX_RECEIVED_DATE).get(), "Received")
+ : JobDate.getCurrentDate();
+
+ Job job = new Job(jobDescription, expectedCompletionDate, receivedDate, fee);
+
+ return job;
+ }
+
+ private Index parseIndexIfPresent(ArgumentMultimap argMultimap, Prefix indexPrefix) throws ParseException {
+ Index index = (argMultimap.getValue(indexPrefix).isPresent())
+ ? ParserUtil.parseIndex(argMultimap.getValue(indexPrefix).get())
+ : null;
+
+ return index;
+ }
+
+ /**
+ * 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/mycrm/logic/parser/jobs/CompleteJobCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/jobs/CompleteJobCommandParser.java
new file mode 100644
index 00000000000..1ed454c0add
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/jobs/CompleteJobCommandParser.java
@@ -0,0 +1,46 @@
+package seedu.mycrm.logic.parser.jobs;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.jobs.CompleteJobCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.job.JobDate;
+
+public class CompleteJobCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the CompleteJobCommand
+ * and returns a CompleteJobCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public CompleteJobCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ String trimmedArgs = args.trim();
+ String[] splitArgs = trimmedArgs.split(" ", 2);
+ Index index = parseIndex(splitArgs);
+ JobDate completionDate = parseCompletionDate(splitArgs);
+
+ return new CompleteJobCommand(index, completionDate);
+ }
+
+ private Index parseIndex(String[] args) throws ParseException {
+ try {
+ String indexString = args[0];
+ Index index = ParserUtil.parseIndex(indexString);
+ return index;
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, CompleteJobCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+ private JobDate parseCompletionDate(String[] args) throws ParseException {
+ JobDate completionDate = (args.length == 2)
+ ? ParserUtil.parseJobDate(args[1], "Completion")
+ : JobDate.getCurrentDate();
+ return completionDate;
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/jobs/DeleteJobCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/jobs/DeleteJobCommandParser.java
new file mode 100644
index 00000000000..dcfbc2a5ee7
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/jobs/DeleteJobCommandParser.java
@@ -0,0 +1,31 @@
+package seedu.mycrm.logic.parser.jobs;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.jobs.DeleteJobCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteJobCommand object
+ */
+public class DeleteJobCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteJobCommand
+ * and returns a DeleteJobCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteJobCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteJobCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteJobCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/jobs/EditJobCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/jobs/EditJobCommandParser.java
new file mode 100644
index 00000000000..09fdb8a1c38
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/jobs/EditJobCommandParser.java
@@ -0,0 +1,122 @@
+package seedu.mycrm.logic.parser.jobs;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_CONTACT_INDEX;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_EXPECTED_COMPLETION_DATE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_FEE;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_JOB_DESCRIPTION;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_INDEX;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_RECEIVED_DATE;
+
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.jobs.EditJobCommand;
+import seedu.mycrm.logic.parser.ArgumentMultimap;
+import seedu.mycrm.logic.parser.ArgumentTokenizer;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.Prefix;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+public class EditJobCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code args} of arguments in the context of the EditJobCommand
+ * and returns an EditJobCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditJobCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ Prefix[] allPrefixes = { PREFIX_JOB_DESCRIPTION, PREFIX_FEE, PREFIX_EXPECTED_COMPLETION_DATE,
+ PREFIX_CONTACT_INDEX, PREFIX_PRODUCT_INDEX, PREFIX_RECEIVED_DATE };
+
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, allPrefixes);
+
+ Index jobIndex = parseJobIndex(argMultimap);
+
+ EditJobCommand.EditJobDescriptor editJobDescriptor = createEditJobDescriptor(argMultimap);
+
+ return new EditJobCommand(jobIndex, editJobDescriptor);
+ }
+
+ private Index parseJobIndex(ArgumentMultimap argMultimap) throws ParseException {
+ try {
+ return ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditJobCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+ private EditJobCommand.EditJobDescriptor createEditJobDescriptor(ArgumentMultimap argMultimap)
+ throws ParseException {
+
+ EditJobCommand.EditJobDescriptor editJobDescriptor = new EditJobCommand.EditJobDescriptor();
+
+ if (argMultimap.getValue(PREFIX_JOB_DESCRIPTION).isPresent()) {
+ editJobDescriptor.setJobDescription(
+ ParserUtil.parseJobDescription(argMultimap.getValue(PREFIX_JOB_DESCRIPTION).get()));
+ }
+
+ if (argMultimap.getValue(PREFIX_EXPECTED_COMPLETION_DATE).isPresent()) {
+ editJobDescriptor.setExpectedCompletionDate(
+ ParserUtil.parseJobDate(argMultimap.getValue(PREFIX_EXPECTED_COMPLETION_DATE).get(),
+ "Expected Completion"));
+ }
+
+ if (argMultimap.getValue(PREFIX_RECEIVED_DATE).isPresent()) {
+ editJobDescriptor.setReceivedDate(
+ ParserUtil.parseJobDate(argMultimap.getValue(PREFIX_RECEIVED_DATE).get(), "Received"));
+ }
+
+ if (argMultimap.getValue(PREFIX_FEE).isPresent()) {
+ editJobDescriptor.setFee(ParserUtil.parseJobFee(argMultimap.getValue(PREFIX_FEE).get()));
+ }
+
+ if (argMultimap.getValue(PREFIX_CONTACT_INDEX).isPresent()) {
+ parseEntityIndex(argMultimap, PREFIX_CONTACT_INDEX, MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX,
+ editJobDescriptor::setEditContact, editJobDescriptor::setClientIndex);
+ }
+
+ if (argMultimap.getValue(PREFIX_PRODUCT_INDEX).isPresent()) {
+ parseEntityIndex(argMultimap, PREFIX_PRODUCT_INDEX, MESSAGE_INVALID_PRODUCT_DISPLAYED_INDEX,
+ editJobDescriptor::setEditProduct, editJobDescriptor::setProductIndex);
+ }
+
+ if (!editJobDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditJobCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return editJobDescriptor;
+ }
+
+ private void parseEntityIndex (ArgumentMultimap argMultimap, Prefix indexPrefix, String errorMessage,
+ Consumer < Boolean > setEditState, Consumer < Index > setIndex) throws ParseException {
+
+ String indexString = argMultimap.getValue(indexPrefix).get();
+ if (indexString.isEmpty()) {
+ setEditState.accept(true);
+ return;
+ }
+
+ try {
+ setIndex.accept(ParserUtil.parseIndex(indexString));
+ } catch (ParseException e) {
+ throw new ParseException(String.format("%s : %s", errorMessage, e.getMessage()));
+ }
+ }
+
+ /**
+ * 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/mycrm/logic/parser/jobs/FindJobCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/jobs/FindJobCommandParser.java
new file mode 100644
index 00000000000..51bf45e22b8
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/jobs/FindJobCommandParser.java
@@ -0,0 +1,29 @@
+package seedu.mycrm.logic.parser.jobs;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import seedu.mycrm.logic.commands.jobs.FindJobCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.job.JobContainsKeywordsPredicate;
+
+public class FindJobCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindJobCommand
+ * and returns a FindJobCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindJobCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindJobCommand.MESSAGE_USAGE));
+ }
+
+ String[] jobKeywords = trimmedArgs.split("\\s+");
+
+ return new FindJobCommand(new JobContainsKeywordsPredicate(Arrays.asList(jobKeywords)));
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/jobs/ListJobCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/jobs/ListJobCommandParser.java
new file mode 100644
index 00000000000..2c880a13eee
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/jobs/ListJobCommandParser.java
@@ -0,0 +1,55 @@
+package seedu.mycrm.logic.parser.jobs;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.logic.commands.jobs.ListJobCommand.SHOW_ALL_FLAG;
+import static seedu.mycrm.logic.commands.jobs.ListJobCommand.SHOW_COMPLETED_FLAG;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_COMPLETED_JOBS;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_INCOMPLETE_JOBS;
+import static seedu.mycrm.model.Model.PREDICATE_SHOW_ALL_JOBS;
+
+import java.util.function.Predicate;
+
+import seedu.mycrm.logic.commands.jobs.ListJobCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.job.Job;
+
+public class ListJobCommandParser implements Parser {
+ private static final String EMPTY_STRING = "";
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ListJobCommand
+ * and returns an ListJobCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public ListJobCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ String[] flags = trimmedArgs.split("\\s+");
+ String flag = flags[0];
+
+ // Check if more than one flag was provided
+ if (flags.length > 1) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ ListJobCommand.MESSAGE_USAGE));
+ }
+
+ Predicate listJobPredicate = getListJobPredicate(flag);
+
+ return new ListJobCommand(listJobPredicate);
+ }
+
+ private Predicate getListJobPredicate(String flag) throws ParseException {
+ switch (flag) {
+ case EMPTY_STRING:
+ return PREDICATE_SHOW_ALL_INCOMPLETE_JOBS;
+ case SHOW_ALL_FLAG:
+ return PREDICATE_SHOW_ALL_JOBS;
+ case SHOW_COMPLETED_FLAG:
+ return PREDICATE_SHOW_ALL_COMPLETED_JOBS;
+ default:
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ ListJobCommand.MESSAGE_USAGE));
+ }
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/jobs/UndoCompleteJobCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/jobs/UndoCompleteJobCommandParser.java
new file mode 100644
index 00000000000..76f956b7d3e
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/jobs/UndoCompleteJobCommandParser.java
@@ -0,0 +1,26 @@
+package seedu.mycrm.logic.parser.jobs;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.jobs.UndoCompleteJobCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+public class UndoCompleteJobCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the UndoCompleteJobCommand
+ * and returns a UndoCompleteJobCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public UndoCompleteJobCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new UndoCompleteJobCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, UndoCompleteJobCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/mails/AddTemplateCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/mails/AddTemplateCommandParser.java
new file mode 100644
index 00000000000..089e946108f
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/mails/AddTemplateCommandParser.java
@@ -0,0 +1,56 @@
+package seedu.mycrm.logic.parser.mails;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_BODY;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_SUBJECT;
+
+import java.util.stream.Stream;
+
+import seedu.mycrm.logic.commands.mails.AddTemplateCommand;
+import seedu.mycrm.logic.parser.ArgumentMultimap;
+import seedu.mycrm.logic.parser.ArgumentTokenizer;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.Prefix;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.mail.Body;
+import seedu.mycrm.model.mail.Subject;
+import seedu.mycrm.model.mail.Template;
+
+/**
+ * Parses input arguments and creates a new AddTemplateCommand object
+ */
+public class AddTemplateCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddTemplateCommand
+ * and returns an AddTemplateCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddTemplateCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_SUBJECT, PREFIX_BODY);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_SUBJECT, PREFIX_BODY)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTemplateCommand.MESSAGE_USAGE));
+ }
+
+ Subject subject = ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT).get());
+ Body body = ParserUtil.parseBody(argMultimap.getValue(PREFIX_BODY).get());
+
+ Template template = new Template(subject, body);
+
+ return new AddTemplateCommand(template);
+ }
+
+ /**
+ * 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/mycrm/logic/parser/mails/DeleteTemplateCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/mails/DeleteTemplateCommandParser.java
new file mode 100644
index 00000000000..ca75cb833f0
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/mails/DeleteTemplateCommandParser.java
@@ -0,0 +1,32 @@
+package seedu.mycrm.logic.parser.mails;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.mails.DeleteTemplateCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteTemplateCommand object
+ */
+public class DeleteTemplateCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteTemplateCommand
+ * and returns a DeleteTemplateCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteTemplateCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteTemplateCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTemplateCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/mails/EditTemplateCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/mails/EditTemplateCommandParser.java
new file mode 100644
index 00000000000..5c666650d49
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/mails/EditTemplateCommandParser.java
@@ -0,0 +1,53 @@
+package seedu.mycrm.logic.parser.mails;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_BODY;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_SUBJECT;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.mails.EditTemplateCommand;
+import seedu.mycrm.logic.parser.ArgumentMultimap;
+import seedu.mycrm.logic.parser.ArgumentTokenizer;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+
+public class EditTemplateCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditTemplateCommand
+ * and returns an EditTemplateCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditTemplateCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_SUBJECT, PREFIX_BODY);
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditTemplateCommand.MESSAGE_USAGE), pe);
+ }
+
+ EditTemplateCommand.EditTemplateDescriptor editTemplateDescriptor =
+ new EditTemplateCommand.EditTemplateDescriptor();
+ if (argMultimap.getValue(PREFIX_SUBJECT).isPresent()) {
+ editTemplateDescriptor.setSubject(ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT).get()));
+ }
+ if (argMultimap.getValue(PREFIX_BODY).isPresent()) {
+ editTemplateDescriptor.setBody(ParserUtil.parseBody(argMultimap.getValue(PREFIX_BODY).get()));
+ }
+
+ if (!editTemplateDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditTemplateCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditTemplateCommand(index, editTemplateDescriptor);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/mails/FindTemplateCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/mails/FindTemplateCommandParser.java
new file mode 100644
index 00000000000..ca097389779
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/mails/FindTemplateCommandParser.java
@@ -0,0 +1,35 @@
+package seedu.mycrm.logic.parser.mails;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import seedu.mycrm.logic.commands.mails.FindTemplateCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.mail.SubjectContainsKeywordsPredicate;
+
+/**
+ * Parses input arguments and creates a new FindTemplateCommand object
+ */
+public class FindTemplateCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindTemplateCommand
+ * and returns a FindTemplateCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindTemplateCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTemplateCommand.MESSAGE_USAGE));
+ }
+
+ String[] subjectKeywords = trimmedArgs.split("\\s+");
+
+ return new FindTemplateCommand(new SubjectContainsKeywordsPredicate(Arrays.asList(subjectKeywords)));
+ }
+
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/mails/MailCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/mails/MailCommandParser.java
new file mode 100644
index 00000000000..2ab7ea9f220
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/mails/MailCommandParser.java
@@ -0,0 +1,39 @@
+package seedu.mycrm.logic.parser.mails;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_JOB_INDEX;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_TEMPLATE_INDEX;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.mails.MailCommand;
+import seedu.mycrm.logic.parser.ArgumentMultimap;
+import seedu.mycrm.logic.parser.ArgumentTokenizer;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new MailCommand object
+ */
+public class MailCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the MailCommand
+ * and returns a MailCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public MailCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_JOB_INDEX, PREFIX_TEMPLATE_INDEX);
+
+ if (ArgumentTokenizer.arePrefixesNotPresent(argMultimap, PREFIX_JOB_INDEX, PREFIX_TEMPLATE_INDEX)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MailCommand.MESSAGE_USAGE));
+ }
+
+ Index jobIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_JOB_INDEX).get());
+ Index templateIndex = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_TEMPLATE_INDEX).get());
+
+ return new MailCommand(jobIndex, templateIndex);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/products/AddProductCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/products/AddProductCommandParser.java
new file mode 100644
index 00000000000..7f24f2cab7c
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/products/AddProductCommandParser.java
@@ -0,0 +1,54 @@
+package seedu.mycrm.logic.parser.products;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_DESCRIPTION;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_MANUFACTURER;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_NAME;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_TYPE;
+
+import java.util.Optional;
+
+import seedu.mycrm.logic.commands.products.AddProductCommand;
+import seedu.mycrm.logic.parser.ArgumentMultimap;
+import seedu.mycrm.logic.parser.ArgumentTokenizer;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.product.Description;
+import seedu.mycrm.model.product.Manufacturer;
+import seedu.mycrm.model.product.Product;
+import seedu.mycrm.model.product.ProductName;
+import seedu.mycrm.model.product.Type;
+
+/** Parses input arguments and creates a AddProductCommand object. */
+public class AddProductCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the {@code AddProductCommand}
+ * and returns a {@code AddProductCommand} object for execution.
+ *
+ * @throws ParseException if the user input does not conform to the expected format.
+ */
+ @Override
+ public AddProductCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ ArgumentMultimap argMap = ArgumentTokenizer.tokenize(args, PREFIX_PRODUCT_NAME, PREFIX_PRODUCT_TYPE,
+ PREFIX_PRODUCT_MANUFACTURER, PREFIX_PRODUCT_DESCRIPTION);
+
+ // Checks and create product name.
+ Optional nameWrapper = argMap.getValue(PREFIX_PRODUCT_NAME);
+ ProductName productName;
+ if (nameWrapper.orElse("").length() == 0) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddProductCommand.MESSAGE_USAGE));
+ }
+ productName = ProductName.getName(nameWrapper);
+
+ // Create optional fields
+ Type type = Type.getType(argMap.getValue(PREFIX_PRODUCT_TYPE));
+ Manufacturer manufacturer = Manufacturer.getManufacturer(argMap.getValue(PREFIX_PRODUCT_MANUFACTURER));
+ Description description = Description.getDescription(argMap.getValue(PREFIX_PRODUCT_DESCRIPTION));
+
+ return new AddProductCommand(new Product(productName, type, manufacturer, description));
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/products/DeleteProductCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/products/DeleteProductCommandParser.java
new file mode 100644
index 00000000000..274a077dcad
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/products/DeleteProductCommandParser.java
@@ -0,0 +1,33 @@
+package seedu.mycrm.logic.parser.products;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.products.DeleteProductCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteProductCommand object.
+ */
+public class DeleteProductCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteProductCommand
+ * and returns a DeleteProductCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format.
+ */
+ public DeleteProductCommand parse(String args) throws ParseException {
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(args);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteProductCommand.MESSAGE_USAGE), pe);
+ }
+
+ return new DeleteProductCommand(index);
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/products/EditProductCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/products/EditProductCommandParser.java
new file mode 100644
index 00000000000..2715bd54992
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/products/EditProductCommandParser.java
@@ -0,0 +1,87 @@
+package seedu.mycrm.logic.parser.products;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_DESCRIPTION;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_MANUFACTURER;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_NAME;
+import static seedu.mycrm.logic.parser.CliSyntax.PREFIX_PRODUCT_TYPE;
+
+import java.util.Optional;
+
+import seedu.mycrm.commons.core.index.Index;
+import seedu.mycrm.logic.commands.products.EditProductCommand;
+import seedu.mycrm.logic.parser.ArgumentMultimap;
+import seedu.mycrm.logic.parser.ArgumentTokenizer;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.ParserUtil;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.product.Description;
+import seedu.mycrm.model.product.Manufacturer;
+import seedu.mycrm.model.product.ProductName;
+import seedu.mycrm.model.product.Type;
+
+/** Parses input arguments and creates a EditProductCommand object. */
+public class EditProductCommandParser implements Parser {
+
+ private EditProductCommand.EditProductDescriptor descriptor;
+
+ private ArgumentMultimap map;
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the {@code EditProductCommand}
+ * and returns a {@code EditProductCommand} object for execution.
+ *
+ * @throws ParseException if the user input does not conform to the expected format.
+ */
+ @Override
+ public EditProductCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ this.map = ArgumentTokenizer.tokenize(args, PREFIX_PRODUCT_NAME, PREFIX_PRODUCT_TYPE,
+ PREFIX_PRODUCT_MANUFACTURER, PREFIX_PRODUCT_DESCRIPTION);
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(map.getPreamble());
+ } catch (ParseException e) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditProductCommand.MESSAGE_USAGE), e);
+ }
+
+ descriptor = new EditProductCommand.EditProductDescriptor();
+ editName();
+ editType();
+ editManufacturer();
+ editDescription();
+
+ if (!descriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditProductCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditProductCommand(index, descriptor);
+ }
+
+ private void editName() {
+ Optional nameWrapper = map.getValue(PREFIX_PRODUCT_NAME);
+ if (nameWrapper.orElse("").length() > 0) {
+ // name is not empty
+ descriptor.setProductName(ProductName.getName(nameWrapper));
+ }
+ }
+
+ private void editType() {
+ descriptor.setType(
+ Type.getType(map.getValue(PREFIX_PRODUCT_TYPE)));
+ }
+
+ private void editManufacturer() {
+ descriptor.setManufacturer(
+ Manufacturer.getManufacturer(map.getValue(PREFIX_PRODUCT_MANUFACTURER)));
+ }
+
+ private void editDescription() {
+ descriptor.setDescription(
+ Description.getDescription(map.getValue(PREFIX_PRODUCT_DESCRIPTION)));
+ }
+}
diff --git a/src/main/java/seedu/mycrm/logic/parser/products/FindProductCommandParser.java b/src/main/java/seedu/mycrm/logic/parser/products/FindProductCommandParser.java
new file mode 100644
index 00000000000..cc06e353511
--- /dev/null
+++ b/src/main/java/seedu/mycrm/logic/parser/products/FindProductCommandParser.java
@@ -0,0 +1,33 @@
+package seedu.mycrm.logic.parser.products;
+
+import static seedu.mycrm.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import seedu.mycrm.logic.commands.products.FindProductCommand;
+import seedu.mycrm.logic.parser.Parser;
+import seedu.mycrm.logic.parser.exceptions.ParseException;
+import seedu.mycrm.model.product.ProductNameContainsKeywordsPredicate;
+
+/**
+ * Parses input arguments and creates a new FindProductCommand object.
+ */
+public class FindProductCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindProductCommand
+ * and returns a FindProductCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format.
+ */
+ public FindProductCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindProductCommand.MESSAGE_USAGE));
+ }
+
+ String[] nameKeywords = trimmedArgs.split("\\s+");
+
+ return new FindProductCommand(new ProductNameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ }
+}
diff --git a/src/main/java/seedu/mycrm/model/Model.java b/src/main/java/seedu/mycrm/model/Model.java
new file mode 100644
index 00000000000..d88ecdbd687
--- /dev/null
+++ b/src/main/java/seedu/mycrm/model/Model.java
@@ -0,0 +1,270 @@
+package seedu.mycrm.model;
+
+import java.nio.file.Path;
+import java.time.LocalDate;
+import java.util.function.Predicate;
+
+import javafx.collections.ObservableList;
+import seedu.mycrm.commons.core.GuiSettings;
+import seedu.mycrm.model.contact.Contact;
+import seedu.mycrm.model.history.History;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.mail.Mail;
+import seedu.mycrm.model.mail.Template;
+import seedu.mycrm.model.product.Product;
+
+/**
+ * The API of the Model component.
+ */
+public interface Model {
+ /** {@code Predicate} that always evaluate to true */
+ Predicate PREDICATE_SHOW_ALL_CONTACTS = unused -> true;
+ Predicate PREDICATE_SHOW_NOT_HIDDEN_CONTACTS = contact -> !contact.checkIsHidden();
+ Predicate PREDICATE_SHOW_ALL_TEMPLATES = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_PRODUCTS = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_JOBS = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_INCOMPLETE_JOBS = job -> !(job.isCompleted());
+ Predicate PREDICATE_SHOW_ALL_COMPLETED_JOBS = job -> job.isCompleted();
+ Predicate PREDICATE_SHOW_ALL_MONTHLY_COMPLETED_JOBS = job -> job.isCompletedThisMonth(LocalDate.now());
+ Predicate PREDICATE_SHOW_ALL_HISTORIES = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_MAILS = unused -> true;
+
+ /**
+ * Replaces user prefs data with the data in {@code userPrefs}.
+ */
+ void setUserPrefs(ReadOnlyUserPrefs userPrefs);
+
+ /**
+ * Returns the user prefs.
+ */
+ ReadOnlyUserPrefs getUserPrefs();
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Sets the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+
+ /**
+ * Returns the user prefs' myCrm file path.
+ */
+ Path getMyCrmFilePath();
+
+ /**
+ * Sets the user prefs' myCrm file path.
+ */
+ void setMyCrmFilePath(Path myCrmFilePath);
+
+ /**
+ * Replaces myCrm data with the data in {@code myCrm}.
+ */
+ void setMyCrm(ReadOnlyMyCrm myCrm);
+
+ /** Returns the MyCrm */
+ ReadOnlyMyCrm getMyCrm();
+
+ /**
+ * Returns true if a contact with the same identity as {@code contact} exists in the myCrm.
+ */
+ boolean hasContact(Contact contact);
+
+ /**
+ * Returns true if a template with the same identity as {@code template} exists in the myCrm.
+ */
+ boolean hasTemplate(Template toAdd);
+
+ /**
+ * Returns true if a job with the same identity as {@code job} exists in the myCrm.
+ */
+ boolean hasJob(Job toAdd);
+
+ /**
+ * Deletes the given contact.
+ * The contact must exist in the myCrm.
+ */
+ void deleteContact(Contact target);
+
+ /**
+ * Deletes the given job.
+ * The job must exist in the myCrm.
+ */
+ void deleteJob(Job target);
+
+ /**
+ * Adds the given contact.
+ * {@code contact} must not already exist in the myCrm.
+ */
+ void addContact(Contact contact);
+
+ /**
+ * Adds the given template.
+ * {@code template} must not already exist in the myCrm.
+ */
+ void addTemplate(Template template);
+
+ /**
+ * Deletes the given template.
+ * The template must exist in the myCrm.
+ */
+ void deleteTemplate(Template target);
+
+ /**
+ * Adds the given mail.
+ * {@code mail} must not already exist in the myCrm.
+ */
+ void addMail(Mail mail);
+
+ /**
+ * Replaces the given template {@code target} with {@code editedTemplate}.
+ * {@code target} must exist in the myCrm.
+ * The template identity of {@code editedTemplate} must not be the same as another existing template
+ * in the myCrm.
+ */
+ void setTemplate(Template target, Template editedTemplate);
+
+ /**
+ * Adds the given job.
+ * {@code job} must not already exist in the myCrm.
+ */
+ void addJob(Job job);
+
+ /**
+ * Replaces the given job {@code target} with {@code editedJob}.
+ * {@code target} must exist in myCrm.
+ * The contact identity of {@code editedJob} must not be the same as another existing
+ * job in the myCRM.
+ */
+ void setJob(Job target, Job editedJob);
+
+ /**
+ * Replaces the given contact {@code target} with {@code editedContact}.
+ * {@code target} must exist in the myCrm.
+ * The contact identity of {@code editedContact} must not be the same as another existing contact
+ * in the myCrm.
+ */
+ void setContact(Contact target, Contact editedContact);
+
+ /**
+ * Hides the given contact {@code target}.
+ * {@code target} must exist in the myCrm.
+ */
+ void hideContact(Contact target);
+
+ /**
+ * Undo hiding the given contact {@code target}.
+ * {@code target} must exist in the myCrm.
+ */
+ void undoHideContact(Contact target);
+
+
+ /**
+ * Returns true if a product with the same identity as {@code product} exists in MyCrm.
+ */
+ boolean hasProduct(Product product);
+
+ /**
+ * Adds the given product.
+ * {@code product} must not already exist in MyCrm.
+ */
+ void addProduct(Product product);
+
+ /**
+ * Deletes the given product.
+ * The product must exist in MyCrm.
+ */
+ void deleteProduct(Product product);
+
+ /**
+ * Replaces the given product {@code target} with {@code editedProduct}.
+ * {@code target} must exist in MyCrm.
+ * The product identity of {@code editedProduct} must not be the same as another existing product
+ * in MyCrm.
+ */
+ void setProduct(Product target, Product editedProduct);
+
+ /**
+ * Adds the entered command.
+ */
+ void addHistory(History history);
+
+ /**
+ * Clears history command data.
+ */
+ void clearHistory();
+
+ double getRevenue(LocalDate date);
+
+ /** Returns an unmodifiable view of the filtered unhidden contact list */
+ ObservableList getFilteredContactList();
+
+ /** Returns an unmodifiable view of the filtered template list */
+ ObservableList getFilteredTemplateList();
+
+ /** Returns an unmodifiable view of the filtered mail list */
+ ObservableList getFilteredMailList();
+
+ /** Returns an unmodifiable view of the filtered product list */
+ ObservableList getFilteredProductList();
+
+ /** Returns an unmodifiable view of the filtered product list */
+ ObservableList getFilteredTopThreeProductList();
+
+ /** Returns an unmodifiable view of the filtered job list */
+ ObservableList getFilteredJobList();
+
+ /** Returns an unmodifiable view of the filtered incomplete job list */
+ ObservableList getFilteredIncompleteJobList();
+
+ /** Returns an unmodifiable view of the filtered job list */
+ ObservableList getFilteredAllJobList();
+
+ /** Returns an unmodifiable view of the filtered monthly completed job list */
+ ObservableList getFilteredMonthlyCompletedJobList();
+
+ /** Returns an unmodifiable view of the filtered history command list */
+ ObservableList getFilteredHistoryList();
+
+ /** Returns the latest predicate of the filtered job list */
+ Predicate getLatestJobPredicate();
+
+ /**
+ * Updates the filter of the filtered contact list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredContactList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered contact list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredTemplateList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered mail list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredMailList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered product list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredProductList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered job list to filter by the given {@code predicate}. Update the latest job
+ * predicate.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredJobList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered history command list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredHistoryList(Predicate predicate);
+}
diff --git a/src/main/java/seedu/mycrm/model/ModelManager.java b/src/main/java/seedu/mycrm/model/ModelManager.java
new file mode 100644
index 00000000000..3244254a6d2
--- /dev/null
+++ b/src/main/java/seedu/mycrm/model/ModelManager.java
@@ -0,0 +1,388 @@
+package seedu.mycrm.model;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.mycrm.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.nio.file.Path;
+import java.time.LocalDate;
+import java.util.function.Predicate;
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
+import seedu.mycrm.commons.core.GuiSettings;
+import seedu.mycrm.commons.core.LogsCenter;
+import seedu.mycrm.model.contact.Contact;
+import seedu.mycrm.model.history.History;
+import seedu.mycrm.model.job.Job;
+import seedu.mycrm.model.mail.Mail;
+import seedu.mycrm.model.mail.Template;
+import seedu.mycrm.model.product.Product;
+
+/**
+ * Represents the in-memory model of the myCrm data.
+ */
+public class ModelManager implements Model {
+ private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
+
+ private final MyCrm myCrm;
+ private final UserPrefs userPrefs;
+ private final FilteredList filteredContacts;
+ private final FilteredList filteredTemplates;
+ private final FilteredList filteredMails;
+ private final FilteredList filteredJobs;
+ private final FilteredList filteredIncompleteJob;
+ private final FilteredList filteredAllJobs;
+ private final FilteredList filteredMonthlyCompletedJobs;
+ private final FilteredList filteredProducts;
+ private final FilteredList