diff --git a/.gitignore b/.gitignore index 284c4ca7cd9..8bb5051a94a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /.gradle/ /build/ src/main/resources/docs/ +bin/ # IDEA files /.idea/ diff --git a/README.md b/README.md index 13f5c77403f..21ccac543d3 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,21 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2324S2-CS2103T-T17-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S2-CS2103T-T17-4/tp/actions) +[![codecov](https://codecov.io/gh/AY2324S2-CS2103T-T17-4/tp/graph/badge.svg?token=LPV8FKMOGM)](https://codecov.io/gh/AY2324S2-CS2103T-T17-4/tp) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). + +# FnBuddy +FnBuddy is an innovative employee contact management application designed specifically for restaurant managers. +It offers a user-friendly interface (both CLI and GUI) that allows you to effortlessly create, manage, +and maintain contact records for all your part-time employees. With FnBuddy, you can store essential information such as +contact details, banking information, and work schedules, ensuring efficient communication and accurate payroll +calculations. + +Example usages: + * As a restaurant manager, I can store the contact and banking details of my part-time employees for easy referral when I want to contact them or transfer their salary. + * As a restaurant manager, I can remove the details of my part-time employees so that I can free up space in my address book. + * As a restaurant manager, I can view the contacts of all my part-time employees so that I can have an overview of all contacts in my address book. + * As a restaurant manager, I can use the application to store the accumulated work hours of my part-time employee for later use of salary calculation. + +For the detailed documentation of this project, see the **[FnBuddy Product Website](https://ay2324s2-cs2103t-t17-4.github.io/tp/)**. diff --git a/build.gradle b/build.gradle index a2951cc709e..f8832c5f749 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,10 @@ test { finalizedBy jacocoTestReport } +run { + enableAssertions = true +} + task coverage(type: JacocoReport) { sourceDirectories.from files(sourceSets.main.allSource.srcDirs) classDirectories.from files(sourceSets.main.output) @@ -66,7 +70,7 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'fnbuddy.jar' } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..ec2f3f01702 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,53 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Chua Joon Peng, Gabriel - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[homepage](https://www.linkedin.com/in/gabriel-chua-087543229/)] +[[github](https://github.com/1rbg)] +[[portfolio](team/gabrielchua.md)] -* Role: Project Advisor +* Role: Developer +* Responsibilities: Documentation + UI -### Jane Doe +### Teo Hao Wei - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/h4ow3i)] +[[portfolio](team/teohaowei.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: Code Quality -### Johnny Doe +### Hue Koh - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/huekoh)] +[[portfolio](team/huekoh.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Task Scheduling + UI -### Jean Doe +### Martin Ng Jinn Kai - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/martinng01)] +[[portfolio](team/martinng.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Testing -### James Doe +### Jin Xunze - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/jxunze)] +[[portfolio](team/jinxunze.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Integration diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 1b56bb5d31b..a2e6a80fff2 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,28 +2,57 @@ layout: page title: Developer Guide --- + * Table of Contents -{:toc} + * [Acknowledgements](#acknowledgements) + * [Setting up, getting started](#setting-up-getting-started) + * [Design](#design) + * [Architecture](#architecture) + * [UI component](#ui-component) + * [Logic component](#logic-component) + * [Model component](#model-component) + * [Storage component](#storage-component) + * [Common classes](#common-classes) + * [Implementation](#implementation) + * [\[Implemented\] Schedule feature](#implemented-schedule-feature) + * [Proposed Implementation](#proposed-implementation) + * [\[Implemented\] Contact archiving](#implemented-contact-archiving) + * [Proposed Implementation](#proposed-implementation) + * [\[Proposed\] Undo/redo feature](#proposed-undoredo-feature) + * [Proposed Implementation](#proposed-implementation) + * [Design considerations:](#design-considerations) + * [Documentation, logging, testing, configuration, dev-ops](#documentation-logging-testing-configuration-dev-ops) + * [Appendix: Requirements](#appendix-requirements) + * [Product scope](#product-scope) + * [User stories](#user-stories) + * [Use cases](#use-cases) + * [Non-Functional Requirements](#non-functional-requirements) + * [Glossary](#glossary) + * [Appendix: Instructions for manual testing](#appendix-instructions-for-manual-testing) + * [Launch and shutdown](#launch-and-shutdown) + * [Deleting a person](#deleting-a-person) -------------------------------------------------------------------------------------------------------------------- -## **Acknowledgements** +## Acknowledgements -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +* Calendar for Schedule feature adapted from: https://gist.github.com/Da9el00/f4340927b8ba6941eb7562a3306e93b6 -------------------------------------------------------------------------------------------------------------------- -## **Setting up, getting started** +## Setting up, getting started Refer to the guide [_Setting up and getting started_](SettingUp.md). -------------------------------------------------------------------------------------------------------------------- -## **Design** +## Design
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [ +_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create +and edit diagrams.
### Architecture @@ -36,7 +65,11 @@ Given below is a quick overview of main components and how they interact with ea **Main components of the architecture** -**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down. +**`Main`** (consisting of +classes [`Main`](https://github.com/AY2324S2-CS2103T-T17-4/tp/tree/master/src/main/java/seedu/address/Main.java) +and [`MainApp`](https://github.com/AY2324S2-CS2103T-T17-4/tp/tree/master/src/main/java/seedu/address/MainApp.java)) is +in charge of the app launch and shut down. + * At app launch, it initializes the other components in the correct sequence, and connects them up with each other. * At shut down, it shuts down the other components and invokes cleanup methods where necessary. @@ -51,16 +84,21 @@ The bulk of the app's work is done by the following four components: **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues +the command `delete 98765432`. Each of the four main components (also shown in the diagram above), * defines its *API* in an `interface` with the same name as the Component. -* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding + API `interface` mentioned in the previous point. -For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. +For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using +the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component +through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the +implementation of a component), as illustrated in the (partial) class diagram below. @@ -68,13 +106,21 @@ The sections below give more details of each component. ### UI component -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +The **API** of this component is specified +in [`Ui.java`](https://github.com/AY2324S2-CS2103T-T17-4/tp/tree/master/src/main/java/seedu/address/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. +The UI consists of a `MainWindow` that is made up of parts +e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, +inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the +visible GUI. -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that +are in the `src/main/resources/view` folder. For example, the layout of +the [`MainWindow`](https://github.com/AY2324S2-CS2103T-T17-4/tp/tree/master/src/main/java/seedu/address/ui/MainWindow.java) +is specified +in [`MainWindow.fxml`](https://github.com/AY2324S2-CS2103T-T17-4/tp/tree/master/src/main/resources/view/MainWindow.fxml) The `UI` component, @@ -85,25 +131,29 @@ The `UI` component, ### Logic component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +The **API** of this component is specified in [`Logic.java`](https://github.com/AY2324S2-CS2103T-T17-4/tp/tree/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: -The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example. +The sequence diagram below illustrates the interactions within the `Logic` component, taking +`execute("delete 98765432")` API call as an example. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `delete 98765432` Command](images/DeleteSequenceDiagram.png)
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
How the `Logic` component works: -1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. +1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates + a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. +1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which + is executed by the `LogicManager`. 1. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
- Note that although this is shown as a single step in the diagram above (for simplicity), in the code it can take several interactions (between the command object and the `Model`) to achieve. + Note that although this is shown as a single step in the diagram above (for simplicity), in the code it can take + several interactions (between the command object and the `Model`) to achieve. 1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: @@ -111,11 +161,18 @@ Here are the other classes in `Logic` (omitted from the class diagram above) tha How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. + +* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a + placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse + the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as + a `Command` object. +* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` + interface so that they can be treated similarly where possible e.g, during testing. ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) + +**API +** : [`Model.java`](https://github.com/AY2324S2-CS2103T-T17-4/tp/tree/master/src/main/java/seedu/address/model/Model.java) @@ -123,9 +180,14 @@ How the parsing works: The `Model` component, * stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. -* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. -* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) +* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which + is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to + this list so that the UI automatically updates when the data in the list change. +* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as + a `ReadOnlyUserPref` objects. +* stores a `Schedule` object that represents the employees' work schedule. +* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they + should make sense on their own without depending on other components)
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
@@ -133,17 +195,21 @@ The `Model` component,
- ### Storage component -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API +** : [`Storage.java`](https://github.com/AY2324S2-CS2103T-T17-4/tp/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). -* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) + +* can save both address book data and user preference data in JSON format, and read them back into corresponding + objects. +* inherits from all of `AddressBookStorage`, `UserPrefStorage` and `ScheduleStorage`, 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 @@ -151,33 +217,102 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa -------------------------------------------------------------------------------------------------------------------- -## **Implementation** +## Implementation This section describes some noteworthy details on how certain features are implemented. +### \[Implemented\] Schedule feature +#### Proposed Implementation + +The current implementation of the schedule feature is facilitated by the `Schedule` interface. It contains a +`Set`, which contains the `ScheduleDate` objects that represent the dates in the schedule and hold a +list of who is working on that particular date. + +Given below is an example usage scenario and how the schedule mechanism behaves at each step. + +Step 1. The user launches the application for the first time. A concrete class implementing `Schedule` will be +initialised with an empty set of `ScheduleDate` objects. The `ModelManager` is then initialised with the `Schedule`. + +Step 2. The user executes `schedule 98765432 2024-04-15` command to add a schedule entry for the person with phone +number `98765432` on the date `2024-04-15`. The old `Schedule` object is then updated in the `Model` through the calling +of `Model#addPersonToSchedule()`, which creates a new `ScheduleDate` object that corresponds to the date +`2024-04-15`, and adds the person with phone number `98765432` to the list of people working on that date. The UI +changes screen to show the updated schedule. + + + +Step 3. The user decides that he would like to add another person on the same date. The user executes `schedule +92345678 2024-04-15` command to add a schedule entry for the person with phone number `92345678` on the date +`2024-04-15`. The old `Schedule` object is then updated in the `Model` through the calling of +`Model#addPersonToSchedule()`, which uses the existing `ScheduleDate` object associated with `2024-04-15` that +corresponds to the date `2024-04-15`, and adds the person with phone number `98765432` to the list of people working +on that date. The UI changes screen to show the updated schedule. + +Step 4. The user decides that he would like to remove the schedule entry. The user executes `unschedule 98765432 +2024-04-15` command to remove the schedule entry for the person with phone number `98765432` on the date +`2024-04-15`. The `unschedule` command removes a person with phone number `98765432` from the existing +`ScheduleDate` with date `2024-04-15`. The UI changes screen to show the updated schedule. + +### \[Implemented\] Contact archiving +#### Proposed Implementation + +The current implementation of contact archiving (and by virtue un-archiving) is facilitated by the `ArchiveCommand` +and `UnarchiveCommand`. It extends `Command` and is responsible for updating the `Person` object's `Archive` field. + +Given below is an example usage scenario and how the archive/unarchive mechanism behaves at each step. + +Step 1. The user launches the application. Existing contacts in storage are loaded into the `AddressBook`, the `ModelManager` +is then initialised with the `AddressBook`. The `Person` objects in the `AddressBook` that have `ArchiveStatus` field set to 'false' +are filtered by `ModelManager` into the `ObservableList` to be displayed in the UI. + +Step 2. The user executes `archive 98765432` command to archive the person in the address book. The `archive` command +creates a new `Person` object that corresponds to all of the selected `Person` object's fields, except for the +`ArchiveStatus` field which it intialises as 'true'. The old `Person` object is then updated in the `AddressBook` through +the calling of `Model#archivePerson()`. The UI updates the `ObservableList` to reflect the changes made. + + + +Step 3. The user decides that he would like to view the archived contact. The user executes `list archive` command to +view all archived contacts. The `list archive` command filters the `Person` objects in the `AddressBook` that have +`ArchiveStatus` field set to 'true' into the `ObservableList` to be displayed in the UI. + +Step 4. The employee of the archived contact decides to return to the company. The user executes `unarchive 98765432` +command to unarchive the person in the address book. The `unarchive` command creates a new `Person` object that +corresponds to all of the selected `Person` object's fields, except for the `ArchiveStatus` field which it intialises +as 'false'. The old `Person` object is then updated in the `AddressBook` through the calling of `Model#archivePerson()`. + ### \[Proposed\] Undo/redo feature #### Proposed Implementation -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo +history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the +following operations: -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +* `VersionedAddressBook#commit()`— Saves the current address book state in its history. +* `VersionedAddressBook#undo()`— Restores the previous address book state from its history. +* `VersionedAddressBook#redo()`— Restores a previously undone address book state from its history. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` +and `Model#redoAddressBook()` respectively. Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the +initial address book state, and the `currentStatePointer` pointing to that single address book state. ![UndoRedoState0](images/UndoRedoState0.png) -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command +calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes +to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book +state. ![UndoRedoState1](images/UndoRedoState1.png) -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +Step 3. The user executes `add n/David …​` to add a new person. The `add` command also +calls `Model#commitAddressBook()`, causing another modified address book state to be saved into +the `addressBookStateList`. ![UndoRedoState2](images/UndoRedoState2.png) @@ -185,7 +320,9 @@ Step 3. The user executes `add n/David …​` to add a new person. The `add` co -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing +the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` +once to the left, pointing it to the previous address book state, and restores the address book to that state. ![UndoRedoState3](images/UndoRedoState3.png) @@ -206,17 +343,23 @@ Similarly, how an undo operation goes through the `Model` component is shown bel ![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png) -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once +to the right, pointing to the previously undone state, and restores the address book to that state.
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5. The user then decides to execute the command `list`. 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. ![UndoRedoState4](images/UndoRedoState4.png) -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not +pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be +purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern +desktop applications follow. ![UndoRedoState5](images/UndoRedoState5.png) @@ -229,24 +372,19 @@ The following activity diagram summarizes what happens when a user executes a ne **Aspect: How undo & redo executes:** * **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. + * Pros: Easy to implement. + * Cons: May have performance issues in terms of memory usage. * **Alternative 2:** Individual command knows how to undo/redo by itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. + * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). + * Cons: We must ensure that the implementation of each individual command are correct. _{more aspects and alternatives to be added}_ -### \[Proposed\] Data archiving - -_{Explain here how the data archiving feature will be implemented}_ - - -------------------------------------------------------------------------------------------------------------------- -## **Documentation, logging, testing, configuration, dev-ops** +## Documentation, logging, testing, configuration, dev-ops * [Documentation guide](Documentation.md) * [Testing guide](Testing.md) @@ -256,81 +394,155 @@ _{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Requirements** +## Appendix: Requirements ### Product scope **Target user profile**: -* has a need to manage a significant number of contacts +* has a need to manage a significant number of employee contacts and banking details +* has a need to track employee worked hours +* has a need to tabulate payroll for employees with different pay rates * 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**: manage employee salary disbursement faster than a typical exel sheet with manual calculations. ### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|----------------------------------------------------------------|----------------------------------------------------|-----------------------------------------------------------------------| +| `* *` | new user | see a tutorial and usage instructions | familiarise with FnBuddy' features | +| `* * *` | user | add an employee contact with banking details | quickly access the employee's banking details for salary disbursement | +| `* * *` | user | delete an employee contact | remove entries that I no longer need | +| `* * *` | user | view all employee contacts | | +| `* *` | user | track an employee's weekly worked hours | access it for employee salary calculation | +| `* *` | user handling employees with a variety of employment contracts | tag an employee contact with their employment type | retrieve the salary rate of the employee | +| `* *` | user with human error tendencies | retrieve an employee's calculated pay | avoid paying out an incorrect salary amount | +| `* *` | user | edit an employee contact details | keep the employee's details up to date | +| `* *` | user | schedule my employee shifts | plan workload more easily | +| `*` | user with many employees | sort employees contacts by name | locate the employee contact easily | +| `*` | user with potential returning employees | archive an employee contact | reopen the employee's details when they return easily | +| `*` | user with forgetfulness | search for contacts by keyword | find contacts without needing to provide their full name | +| `*` | user with many employees | filter employee contacts by tag(s) | identify which employee(s) are of that employment type | *{More to be added}* ### 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 `FnBuddy` and the **Actor** is the `user`, unless specified otherwise) + +**Use case: Add an employee** + +**MSS** + +1. User requests to add employee contact +2. FnBuddy requests contact information of employee +3. User provides required information +4. FnBuddy adds the employee contact + + Use case ends. + +**Extensions** + +* 3a. The given contact information is invalid. + + * 3a1. FnBuddy shows an error message. -**Use case: Delete a person** + Use case resumes at step 2. + +**Use case: Delete an employee** **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to delete an employee contact +2. FnBuddy requests employee contact +3. User provides required information +4. FnBuddy deletes the employee contact Use case ends. **Extensions** -* 2a. The list is empty. +* 2a. The contact book is empty. + * 2a1. FnBuddy shows an error message. + + Use case ends - Use case ends. -* 3a. The given index is invalid. +* 3a. The given contact information is invalid. - * 3a1. AddressBook shows an error message. + * 3a1. FnBuddy shows an error message. Use case resumes at step 2. -*{More to be added}* +**Use case: List all employee contacts** -### Non-Functional Requirements +**MSS** + +1. User requests to list all employee contacts +2. FnBuddy lists all employee contacts + + Use case ends. + +**Extensions** + +* 2a. The contact book is empty. + * 2a1. FnBuddy shows an error message. + + Use case ends + +**Use case: Track employee's work hours** + +**MSS** + +1. User requests to add employee's work hours +2. FnBuddy requests employee contact and work hours +3. User provides required information +4. FnBuddy tracks the employee's work hours + + Use case ends. + +**Extensions** + +* 2a. The contact book is empty. + * 2a1. FnBuddy shows an error message. + + Use case ends + + +* 3a. The given contact information or working hours is invalid. + + * 3a1. FnBuddy shows an error message. + + Use case resumes at step 2. -1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. *{More to be added}* +### Non-Functional Requirements + +1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. +2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be + able to accomplish most of the tasks faster using commands than using the mouse. +4. The app should be highly reliable, minimizing downtime and ensuring continuous availability during operational hours. +5. It should have built-in mechanisms for data backup and recovery to prevent loss of employee contact information. + ### Glossary * **Mainstream OS**: Windows, Linux, Unix, MacOS * **Private contact detail**: A contact detail that is not meant to be shared with others +* **Worked hours**: The number of hours an employee has spent working in a month +* **Bank Details**: Account number for salary disbursement, 7-11 digits in length. -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Instructions for manual testing** +## Appendix: Instructions for manual testing Given below are instructions to test the app manually. @@ -343,15 +555,16 @@ testers are expected to do more *exploratory* testing. 1. Initial launch - 1. Download the jar file and copy into an empty folder + 1. Download the jar file and copy into an empty folder - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be + optimum. 1. Saving window preferences - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 1. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
+ 1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained. 1. _{ more test cases …​ }_ @@ -360,23 +573,15 @@ testers are expected to do more *exploratory* testing. 1. Deleting a person while all persons are being shown - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. - 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. + 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. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + 1. Test case: `delete 0`
+ Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. - -1. _{ more test cases …​ }_ + 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. -### Saving data - -1. Dealing with missing/corrupted data files - - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ - -1. _{ more test cases …​ }_ diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 7abd1984218..90caab92ace 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,198 +1,547 @@ ---- -layout: page -title: User Guide ---- +# FnBuddy User Guide + +Welcome to the FnBuddy User Guide! This comprehensive guide is designed to help you navigate and utilise the FnBuddy part-time employee contact management application with ease. +Whether you need to create, update, schedule or retrieve the payroll of your employees, or simply wish to navigate the application seamlessly, this guide will equip you with the knowledge to do so effectively. + +Our guide assumes that you are a restaurant manager with a basic understanding of using software applications, and are familiar with common restaurant operations and terminology. + +## Table of Contents + +- [FnBuddy User Guide](#fnbuddy-user-guide) + - [Table of Contents](#table-of-contents) + - [Introduction to FnBuddy](#introduction-to-fnbuddy) + - [Quick Start](#quick-start) + - [GUI Components](#gui-components) + - [Pages](#pages) + - [Command Type](#command-type) + - [Features](#features) + - [Notes about the command format](#notes-about-the-command-format) + - [Adding a person `add`](#adding-a-person-add) + - [Listing contacts `list`](#listing-contacts-list) + - [Deleting a person `delete`](#deleting-a-person-delete) + - [Editing a person `edit`](#editing-a-person-edit) + - [Locating a person by name `find`](#locating-a-person-by-name-find) + - [Clear all contacts `clear`](#clear-all-contacts-clear) + - [Exiting the program `exit`](#exiting-the-program-exit) + - [Archiving the person `archive`](#archiving-the-person-archive) + - [Unarchive the person `unarchive`](#unarchive-the-person-unarchive) + - [Retrieving payroll `payroll`](#retrieving-payroll-payroll) + - [Schedule employees `schedule`](#schedule-employees-schedule) + - [Unschedule employees `unschedule`](#unschedule-employees-unschedule) + - [Saving the data](#saving-the-data) + - [Modifying saved data](#modifying-saved-data) + - [Known Issues](#known-issues) + - [Planned Enhancements](#planned-enhancements) + - [Adding a `view` feature](#adding-a-view-feature) + - [Enhancements to Archive Feature](#enhancements-to-archive-feature) + - [More Informative List Command Feedback](#more-informative-list-command-feedback) + - [Enhancements to Scheduling and Payroll Feature](#enhancements-to-scheduling-and-payroll-feature) + - [Separating Multiple Tags](#separating-multiple-tags) + - [Glossary](#glossary) + - [FAQ](#faq) + - [Command summary](#command-summary) + +Click on any item in the table of contents to instantly navigate to that specific section. + +
+ +## Introduction to FnBuddy + +Whether you're a seasoned restaurant manager or new to the role, FnBuddy will serve as your trusted companion, empowering you to streamline your operations and enhance your team management capabilities. FnBuddy manage contacts optimised for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). So if you're a fast typer, FnBuddy is just the application for you! + +Features that FnBuddy offers with your needs in mind include: +- **contact creation and management** such as editing and archiving +- **scheduling** of part-time employee contacts on specified dates +- **payroll retrieval** that calculates the total payroll for all employees who worked within a specified date range (currently dependent on their scheduled hours). + +
+ +## Quick Start + +1. Ensure you have [Java 11](#faq) or above installed on your Computer. +2. Download the latest fnbuddy.jar from [here](https://github.com/AY2324S2-CS2103T-T17-4/tp/releases/latest/). +3. Move the fnbuddy.jar file to the directory you intend to designate as the home folder for your FnBuddy. +4. Open the terminal on your Operating System. + - For Windows, press `Win + R`, type `cmd`, and press Enter. + - For macOS, press `Cmd + Space`, type `Terminal`, and press Enter. + - For Linux, press `Ctrl + Alt + T`. +5. You should see a terminal window similar to the one below open. (The terminal window will look different depending on your Operating System.) + + | ![Terminal](./images/Terminal.png) | + |--------------------------------------------------| + | *Example of a terminal window on a MacOS system* | +6. Type `cd` followed by the path to the folder you copied the fnbuddy.jar file to. + - Windows Example: `cd C:\Users\JohnDoe\Desktop\FnBuddy`. + - MacOS Example: `cd /Users/JohnDoe/Desktop/FnBuddy`. + - Linux Example: `cd /home/JohnDoe/Desktop/FnBuddy`. +7. Type `java -jar fnbuddy.jar` to run the application. +8. An application similar to the one below should appear in a few seconds. For a more detailed explanation of the Graphical User Interface (GUI) and + its components, refer to the [GUI Components](#gui-components). Note how the app contains some sample data. The list of contacts displayed at startup is the main list of unarchived contacts. More details in the [Features](#features) section below. + + | ![UI](./images/Ui.png) | + |------------------------| + | *FnBuddy GUI* | + +9. Type the command in the command box and press Enter to execute it. e.g., typing `help` and pressing Enter will open the help window. + +Some example commands you can try: + +- `add -fn Javier -ln Tan -p 98749874 -s m -pr 10.5 -a 123 Street -b posb 420053040` : Adds a contact named Javier Tan to FnBuddy. +- `list all` : Lists all contacts. +- `list main` : Lists all un-archived contacts. +- `list archive` : Lists all archived contacts. +- `archive 98749874` : Archives the contact associated with the phone number 98749874. +- `delete 98749874` : Deletes the contact associated with the phone number 98749874 from FnBuddy. +- `edit 98749874 -a NUS` : Edits the address of the contact associated with phone number 98749874 to NUS. +- `find james` : Searches the address book for a person whose name matches “james”. +- `clear` : Deletes all contacts. +- `exit` : Exits the app. + +Refer to the [Features](#features) section below for details of each command. + +
+ +## GUI Components +| ![LabelledUi](./images/LabelledUi.png) | +|----------------------------------------| +| *Labelled UI Components* | +The names of the UI components have been labelled in the image above. The components are as follows: +1. **Command Box** - This is where you can type commands to interact with the application. +2. **Feedback Panel** - This panel displays feedback messages to the user. These feedback messages can be success + messages, error messages, or help with commands. +3. **Navigation Buttons** - These buttons allow you to navigate between the different pages of the application. The + buttons are labelled "CONTACTS" and "SCHEDULE". +4. **Results Panel** - This panel displays the currently selected page, based on the last command or button clicked. The + results panel will display the contacts, schedule, or payroll based on the page selected. +5. **Menu Bar** - The menu bar contains the "File" and "Help" menus. The "File" menu contains the "Exit" option, which + allows you to exit the application. The "Help" menu contains the "Help" option, which provides a link to this + user guide for the application. + +
+ +## Pages +There are 3 main pages in the application: +1. **Main Page** - This page shows all the contacts in the application. You can view the details of each contact by clicking on the contact. + + | ![UI](./images/Ui.png) | + |------------------------------| + | *FnBuddy Main Page* | + +
+ +2. **Payroll Page** - This page shows the payroll of all employees for the given date range in the payroll command. + + | ![Payroll Page](./images/PayrollPageSuccess.png) | + |-----------------------------------------------------------------| + | *FnBuddy Payroll Page* | +3. **Schedule Page** - This page shows the schedule of all employees for the next four weeks (including the current + week). + + | ![Schedule Page](./images/SchedulePageSuccess.png) | + |----------------------------------------------------| + | *FnBuddy Schedule Page* | + +### Command Type +With the 3 different pages, different types of commands can also be used to navigate the application. These types are as follows: +1. **Main Page Commands** - These commands when used will navigate to the main page of the application. +2. **Payroll Page Commands** - These commands when used will navigate to the payroll page of the application. +3. **Schedule Page Commands** - These commands when used will navigate to the schedule page of the application. + +
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. - -* Table of Contents -{:toc} +## Features --------------------------------------------------------------------------------------------------------------------- +### Notes about the command format -## Quick start +- Words in UPPER_CASE are the parameters to be supplied by the user. e.g., in `add -fn FIRST_NAME -ln LAST_NAME`, FIRST_NAME and LAST_NAME are parameters which can be used as `add -fn Javier -ln Tan`. +- Items in square brackets are optional. e.g., `-fn FIRST_NAME -ln LAST_NAME [-t TAG]` can be used as `-fn Javier -ln Tan -t/waiter` or as `-fn Javier -ln Tan`. +- Items with `…` after them can be used multiple times, including zero times. e.g., `[-t TAG]…` can be used as `-t cook -t waiter -t dishwasher`, etc. +- Parameters can be in any order. e.g., if the command specifies `-fn FIRST_NAME -ln LAST_NAME`, `-ln LAST_NAME -fn FIRST_NAME` is also acceptable. +- Extraneous parameters for commands that do not take in parameters (such as help, exit, and clear) will be ignored. e.g., if the command specifies `help 123`, it will be interpreted as `help`. +- If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines, as space characters surrounding line-breaks may be omitted when copied over to the application. +- There should be spaces between the flags and the parameters. e.g., `add -fn Javier -ln Tan` is correct, while `add -fn Javier-ln Tan` is incorrect. However, extra spaces are allowed. e.g., `add -fn Javier -ln Tan` is also correct. +- The types of commands are divided into 3 categories: Main Page Commands, Payroll Page Commands, and Schedule Page + Commands. For more details, refer to the [Pages](#Pages) section below. +- If you key in any command that is not recognised by the application, the feedback panel will display an error message + indicating that the command is not recognised. +- If you key in any command that is recognised by the application but has incorrect parameters, the feedback panel will + display an error message indicating that the command has incorrect parameters. Just follow the correct format and + try again! -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). +### Adding a person `add` -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +Adds a person’s contact to FnBuddy. -1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +Format: `add -fn FIRST_NAME -ln LAST_NAME -p PHONE_NUMBER -s SEX -pr PAY_RATE -a ADDRESS [-b BANK_DETAILS] [-t TAG]…` -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: +Command Type: [Main Page Command](#command-type) - * `list` : Lists all contacts. +Example: - * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. +- `add -fn John -ln Doe -p 91860934 -s m -pr 20.50 -a 123 Main St, City` +- `add -fn Jane -ln Smith -p 98765432 -s f -pr 25.50 -a 432 Orchard Road -b posb 123456789 -t waiter -t bartender` - * `delete 3` : Deletes the 3rd contact shown in the current list. +Note: All contacts added are compressed to only show `FIRST_NAME`, `LAST_NAME` and `PHONE_NUMBER` by default. To view all the contact's information, simply click on the GUI contact to expand it. - * `clear` : Deletes all contacts. +*** - * `exit` : Exits the app. +
-1. Refer to the [Features](#features) below for details of each command. +### Listing contacts `list` --------------------------------------------------------------------------------------------------------------------- +Shows a list of contacts in FnBuddy depending on which you'd like to view. On launch of the application, the default list shows all un-archived contacts only. -## Features +Format: `list LIST_TYPE` -
+Command Type: [Main Page Command](#command-type) -**:information_source: Notes about the command format:**
+Example: -* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +- `list all` shows all contacts in FnBuddy. -* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. +| ![List All UI](./images/listall_screenshot.png) | +|------------------------------------------------------------------| +| _Example result panel display for a successful list all command_ | -* Items with `…`​ after them can be used multiple times including zero times.
- e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. +- `list main` shows all un-archived contacts in FnBuddy. -* 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. +| ![List Main UI](./images/listmain_screenshot.png) | +|-------------------------------------------------------------------| +| _Example result panel display for a successful list main command_ | -* 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`. +- `list archive` shows all archived contacts in FnBuddy. -* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. -
+| ![List Archive UI](./images/listarchive_screenshot.png) | +|----------------------------------------------------------------------| +| _Example result panel display for a successful list archive command_ | -### Viewing help : `help` +Note: If unexpected extraneous parameters are added such as `list main 123`, the command will default to `list all`. (eg. you are currently viewing `list all` and want to switch to `list archive`, but typed `list archivee` instead, there will be no visible change on the GUI as it has defaulted to `list all`.) -Shows a message explaning how to access the help page. +Note: We understand that the current feedback messages for the list command may not be sufficient to inform you which list you are currently viewing. As we continue to strive and improve your experience with us in upcoming versions, we seek your understanding on this for FnBuddy's current version and have addressed some possible scenarios in the [FAQ](#FAQ) section. -![help message](images/helpMessage.png) +### Deleting a person `delete` -Format: `help` +Deletes the specified person from FnBuddy. +Format: `delete PHONE_NUMBER` -### Adding a person: `add` +Command Type: [Main Page Command](#command-type) -Adds a person to the address book. +Example: -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +- `delete 91860934` deletes the person with the number 91860934 from FnBuddy. -
:bulb: **Tip:** -A person can have any number of tags (including 0) -
+*** -Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +### Editing a person `edit` -### Listing all persons : `list` +Edits an existing person in FnBuddy. -Shows a list of all persons in the address book. +Format: `edit PHONE_NUMBER [-fn FIRST_NAME] [-ln LAST_NAME] [-p PHONE_NUMBER] [-s SEX] [-pr PAY_RATE] [-a ADDRESS] [-b BANK_DETAILS] [-t TAG]…` -Format: `list` +Command Type: [Main Page Command](#command-type) -### Editing a person : `edit` +Example: -Edits an existing person in the address book. +- `edit 91860934 -a Room 504, Marina Bay Sands -pr 25` Edits the address of the person with the phone number 91860934 to Room 504 Marina Bay Sands, and their pay rate to 25 dollars per hour, respectively. +- `edit 98765432 -t ` Clears all existing tags from the person with the phone number 98765432. Take note to put an additional space after the `-t` flag to clear all tags. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Note: You can only edit contacts that are currently visible in the panel. If you are unable to find the contact you +wish to edit, use the `find` command to locate the contact first, or use the `list all` command to view all contacts +before editing that particular contact. -* 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` +### Locating a person by name `find` -Finds persons whose names contain any of the given keywords. +Finds persons whose names match completely any of the given keywords. The search is case-insensitive. Format: `find KEYWORD [MORE_KEYWORDS]` -* 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` +Command Type: [Main Page Command](#command-type) -Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +Example: -### Deleting a person : `delete` +- `find tan` returns all contacts with names matching `tan`. -Deletes the specified person from the address book. +| ![Find UI](./images/Find_UI.png) | +|--------------------------------------------------------------| +| *Example result panel display for a successful find command* | -Format: `delete INDEX` +*** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index **must be a positive integer** 1, 2, 3, …​ +### Clear all contacts `clear` -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. +Delete all employee contacts. -### Clearing all entries : `clear` +Format: `clear` -Clears all entries from the address book. +Command Type: [Main Page Command](#command-type) -Format: `clear` +**WARNING!** This action is permanent and non-reversible! Make sure that you want to clear FnBuddy before you execute the command. + +*** -### Exiting the program : `exit` +### Exiting the program `exit` Exits the program. Format: `exit` +*** + +### Archiving the person `archive` + +Archives the person's contact so that it is hidden from the main list of contacts. + +Format: `archive PHONE_NUMBER` + +Command Type: [Main Page Command](#command-type) + +Example: + +- `archive 91860934` archives the person with the number 91860934 from FnBuddy's main list of contacts. + +*** + +### Unarchive the person `unarchive` + +Unarchive the person's contact so that it is shown in the main list of contacts. + +Format: `unarchive PHONE_NUMBER` + +Command Type: [Main Page Command](#command-type) + +Example: + +- `unarchive 91860934` un-archives the person with the number 91860934 from FnBuddy's main list of contacts. + +*** + +
+ +### Retrieving payroll `payroll` + +Retrieve employee's payroll for a given start and end date + +Format: `payroll -sd START_DATE -ed END_DATE` where `START_DATE` and `END_DATE` are in the format `YYYY-MM-DD`. + +Command Type: [Payroll Command](#command-type) + +Example: + +- `payroll -sd 2024-04-01 -ed 2024-04-30` calculates the payroll of all employees that have worked within 1st April 2024 and 30th April 2024. + +| ![Payroll Page](./images/PayrollPageSuccess.png) | +|-----------------------------------------------------------------| +| *Example result panel display for a successful payroll command* | + +Note: Employee's payroll is calculated by multiplying 8 to their respective `PAY_RATE`. We are assuming each shift is 8 hours. + +*** + +
+ +### Schedule employees `schedule` + +Adds a person in FnBuddy to the schedule on a specified date. + +Format: `schedule PHONE_NUMBER DATE` where `DATE` is in the format `YYYY-MM-DD`. + +Command Type: [Schedule Command](#command-type) + +Example: +- `schedule 91860934 2024-04-01` Adds the person with the phone number 91860934 to the schedule on 1st April 2024. + +| ![Schedule Page](./images/SchedulePageSuccess.png) | +|----------------------------------------------------------------------| +| *Example result panel display for a successful schedule command* | + +Note: If you add a duplicate entry in the schedule for the same date, the feedback message will not prompt this. +However, the schedule will not be updated with the new entry. + +*** + +
+ +### Unschedule employees `unschedule` + +Removes a person in FnBuddy from the schedule on a specified date. + +Format: `unschedule PHONE_NUMBER DATE` where `DATE` is in the format `YYYY-MM-DD`. + +Command Type: [Schedule Command](#command-type) + +Example: +- `unschedule 91860934 2024-04-01` Removes the person with the phone number 91860934 from the schedule on 1st April 2024. + +| ![Unschedule Page](./images/UnschedulePageSuccess.png) | +|--------------------------------------------------------------------| +| *Example result panel display for a successful unschedule command* | + +*** + +
+ ### Saving the data -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +FnBuddy data is stored in the hard disk automatically after any command that changes the data. Rest assured, there is no need to save manually. + +#### Modifying saved data + +⚠️ **For advanced users only!** + +You may edit the JSON files directly in the data folder if you wish to make changes to the data without using the app. However, +if the JSON files are no longer in the correct format, the app may not be able to read the data correctly and may crash or have unexpected behaviour. + +If you still wish to edit the JSON files, for the schedule.json file, note that there are additional constraints you must follow aside from the format: + +- The contact in the schedule must exist in the main list of contacts i.e. you should not add a new contact that does not exist, in the schedule.json file. +- There should not be duplicate entries for the same contact on the same date. +- There should not be duplicate entries for the same date. + +
+ +## Known Issues + +1. When using multiple screens, if you move the application to a secondary screen and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. + a. Alternatively, for Windows users, you can press Shift and right-click the program icon on the taskbar, Select Move, and use your left or right arrow keys to move the window until the window appears. +2. The same feedback message 'listed all employees' produced by the application when the list commands (`list all`, `list main`, `list archive`) are inputted is used. The current universal feedback message does not provide the user with enough information about which list they are viewing. + There is currently no remedy for this flaw, and it is set to be a future enhancement. +3. The `schedule` and `payroll` feature currently lets you schedule and calculate payroll for employees who are not in the main list of contacts. There is no current remedy for this flaw apart from you as a user being mindful of not scheduling an archived contact. +4. The `payroll` feature currently calculates the payroll based on the pay rate of each employee and assumes that each shift is 8 hours. This may not be accurate for all employees, especially if they work different hours or have different pay rates. +5. The `payroll` feature currently accepts invalid date ranges, such as end dates that are before the start date. This may lead to incorrect payroll calculations. There is no current remedy for this flaw apart from you as a user being mindful of the date ranges you input. + +
+ +## Planned Enhancements + +### Adding a `view` feature + +- In order to cater to the user's preference of a command line interface, we will be adding a `view` feature that will + allow users to view all the details of a contact without having to click on the contact card in the GUI, which is the + current only way to access all the details of a contact. + +### Enhancements to Archive Feature + +- Making the archive status of each person visible to the user on each contact card. This is to improve the usability of the archive feature and + add more differentiation to archived and unarchived contacts, which is currently only differentiated by which list (list main OR list archive) they are viewing. + +### More Informative List Command Feedback -### Editing the data file +- To provide more informative feedback to the user when they use the list command, we will be updating the feedback messages to display the list type that the user is currently viewing. This will help the user to know which list they are currently viewing, as the current feedback message is the same for all list commands. -AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +### Stricter Validations for Schedule and Payroll Features -
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly. -
+- To prevent users from scheduling or calculating payroll for contacts that are supposed to be archived, we will be adding validation checks to ensure that only contacts in the main list can be scheduled or used for payroll calculations. +- To ensure that only valid date ranges are used for payroll calculations, we will be adding validation checks to ensure that the start date is before the end date. -### Archiving data files `[coming in v2.0]` +### Enhanced Flexibility for Schedule Feature -_Details coming soon ..._ +- Currently, the application calendar only allows users to view the next real time four weeks, without the option to toggle to the previous or next months. We will be adding the ability to toggle between months to +allow users to view and schedule employees for any date in the future. This will also allow users more flexibility with the scheduling feature. --------------------------------------------------------------------------------------------------------------------- +- In order to cater to employees having differing shift hours, we will be adding the ability to input and modify the number of hours worked by each employee during a shift. This will allow for more accurate payroll calculations and better tracking of employee work hours as compared to the current restricted 8 hours per shift assumption. + +### Separating Multiple Tags + +- Currently, when multiple tags are added to a contact, they are displayed as a single string. We will be improving the tag interface to allow for better visualisation of tags in the future. + +
+ +## Glossary + +- **CLI** - Command Line Interface + - A text-based interface used to interact with software applications. In the context of this application, the + CLI is used to input commands to manage employee contacts. You can type commands into the command box as shown + below. + + | ![CLI](./images/CommandBox.png) | + |--------------------------------------| + | *Command Box used to input commands* | + +
+ +- **GUI** - Graphical User Interface + - A visual interface that allows users to interact with software applications using graphical elements such as + windows, buttons, and icons. In the context of this application, the GUI provides a visual representation of + the employee contacts and allows users to interact with the application using buttons and text fields. However, + our GUI is not as feature-rich as traditional GUI applications and is designed to be used in conjunction with + the CLI to provide the full functionality of the application. + + | ![GUI](./images/Ui.png) | + |-------------------------------------------------------| + | _How the app displays information in the FnBuddy GUI_ | + +- **FnBuddy** - The name of the application +- **Employee** - A person who works part-time at a restaurant +- **Contact** - A record of an employee in FnBuddy +- **Payroll** - The total amount of money paid to employees for their work + +
## FAQ -**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +- **Q: What is Java 11?** + - A: Java 11 is a release of the [Java](https://en.wikipedia.org/wiki/Java_(programming_language)) programming language and runtime environment. In the context of FnBuddy, + Java 11 is required to be able to run the fnbuddy.jar program that launches the application. For installation + instructions, refer to this [guide](https://docs.oracle.com/en/java/javase/11/install/installation-jdk-microsoft-windows-platforms.html#GUID-A7E27B90-A28D-4237-9383-A58B416071CA) + to install the Java Development Kit (JDK) for Java 11. + +- **Q: How is FnBuddy’s data stored?** + - A: FnBuddy stores its data in local files on your computer (JSON format). + +- **Q: I prefer clicking buttons to navigate applications. Will FnBuddy be upgrading its GUI to be friendlier to such users?** + + - A: As FnBuddy is purposefully targeted towards users who prefer typing, there are currently no plans to add features that support GUI interactions to replace the command-line style of the app. + +- **Q: Is there any quick referral in the app itself for commands that I forget?** + + - A: Click on the help button at the top left of the application’s window for a commands list dropdown. + +- **Q: Can I list down an employee with more than 2 names?** + + - A: As FnBuddy only supports the `FIRST_NAME` `LAST_NAME` format, the best solution currently is to list the middle name with either the first or the last. Example: Chua Xun Hao can be saved as `-fn Xun Hao -ln Chua`. + +- **Q: My employee uses a foreign phone number, how can that be reflected in the app?** + + - A: FnBuddy does not currently support phone number region differentiation and only supports 8-digit inputs after the `-p` flag. In future updates, we will add contact regions as a feature. + +- **Q: Do I need to provide all the details of an employee when creating the contact?** + - A: No, optional details do not need to be added and can be edited into the contact later on if required. Refer to the add contact feature to view which details are compulsory and optional. --------------------------------------------------------------------------------------------------------------------- +- **Q: Can I modify the hours worked of my staff?** + - A: Currently, the hours worked by an employee are calculated based on the schedule. In future updates, we will + add a feature to allow users to modify the hours worked by an employee, you can see more in [Planned + Enhancements](#Planned-Enhancements). -## Known issues +- **Q: I have an archived contact, but I can't find it in the main list. How do I unarchive it?** + - A: Use the `list archive` command to view all archived contacts. From there, you can unarchive the contact by using the `unarchive` command. You may also use the `list all` command to view all contacts, including archived ones. -1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. +- **Q: I have a contact for John Doe in the main list, and a contact for another John Doe in the archive list. How do I differentiate between the two?** + - A: Currently, the only way to differentiate between the two contacts is by viewing the list they are in, as well as clicking on them to expand their details card. We are planning to add a feature that will display the archive status of each contact on the contact card to make it easier to differentiate between archived and unarchived contacts in future versions. --------------------------------------------------------------------------------------------------------------------- +
## 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` +Here's the updated table with the new features added: + +| Command | Description | Format | Examples | +|-------------------------------|------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Adding a person** | Adds a person's contact to FnBuddy. | `add -fn FIRST_NAME -ln LAST_NAME -p PHONE_NUMBER -s SEX -pr PAY_RATE -a ADDRESS [-b BANK_DETAILS] [-t TAG]...` | `add -fn John -ln Doe -p 91860934 -s m -pr 20.50 -a 123 Main St City`
`add -fn Jane -ln Smith -p 98765432 -s f -pr 25.50 -a 432 Orchard Road -b posb 123456789 -t waiter -t bartender` | +| **Listing all persons** | Shows a list of all persons in FnBuddy. | `list LIST_TYPE` | 1. `list all`
2. `list archive`
3. `list main` | +| **Deleting a person** | Deletes the specified person from FnBuddy. | `delete PHONE_NUMBER` | `delete 91860934` | +| **Editing a person** | Edits an existing person in FnBuddy. | `edit PHONE_NUMBER [-fn FIRST_NAME] [-ln LAST_NAME] [-p PHONE_NUMBER] [-s SEX] [-pr PAY_RATE] [-a ADDRESS] [-b BANK_DETAILS] [-t TAG]...` | `edit 91860934 -a Room 504 Marina Bay Sands -pr 25`
`edit 98765432 -t ` | +| **Locating a person by name** | Finds persons whose names match any of the given keywords. | `find KEYWORD [MORE_KEYWORDS]` | `find john tan` | +| **Clear all contacts** | Delete all employee contacts. | `clear` | - | +| **Exiting the program** | Exits the program. | `exit` | - | +| **Archiving a person** | Archives the person's contact so that it is hidden from the main list of contacts. | `archive PHONE_NUMBER` | `archive 91860934` | +| **Un-archiving a person** | Un-archives the person's contact so that it is shown in the main list of contacts. | `unarchive PHONE_NUMBER` | `unarchive 91860934` | +| **Retrieving payroll** | Retrieve employee's payroll for a given start and end date. | `payroll -sd START_DATE -ed END_DATE` | `payroll -sd 2024-04-01 -ed 2024-04-30` | +| **Schedule employees** | Adds a person in FnBuddy to the schedule on a specified date. | `schedule PHONE_NUMBER DATE` | `schedule 91860934 2024-04-01` | +| **Unschedule employees** | Removes a person in FnBuddy from the schedule on a specified date. | `unschedule PHONE_NUMBER DATE` | `unschedule 91860934 2024-04-01` | diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..e63849675e9 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "FnBuddy" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2324S2-CS2103T-T17-4/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index 48b6cc4333c..2f575d2f0a0 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -8,10 +8,10 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "delete 98765432" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delete 98765432") activate logic LOGIC_COLOR logic -[LOGIC_COLOR]> model : deletePerson(p) diff --git a/docs/diagrams/ArchiveSequenceDiagram.puml b/docs/diagrams/ArchiveSequenceDiagram.puml new file mode 100644 index 00000000000..d5acf341921 --- /dev/null +++ b/docs/diagrams/ArchiveSequenceDiagram.puml @@ -0,0 +1,86 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ArchiveCommandParser" as ArchiveCommandParser LOGIC_COLOR +participant "a:ArchiveCommand" as ArchiveCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +participant "p1:Person" as Person1 MODEL_COLOR +participant "p2:Person" as Person2 MODEL_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("archive 98765432") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("archive 98765432") +activate AddressBookParser + +create ArchiveCommandParser +AddressBookParser -> ArchiveCommandParser +activate ArchiveCommandParser + +ArchiveCommandParser --> AddressBookParser +deactivate ArchiveCommandParser + +AddressBookParser -> ArchiveCommandParser : parse("98765432") +activate ArchiveCommandParser + +create ArchiveCommand +ArchiveCommandParser -> ArchiveCommand +activate ArchiveCommand + +ArchiveCommand --> ArchiveCommandParser : +deactivate ArchiveCommand + +ArchiveCommandParser --> AddressBookParser : a +deactivate ArchiveCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ArchiveCommandParser -[hidden]-> AddressBookParser +destroy ArchiveCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> ArchiveCommand : execute(m) +activate ArchiveCommand + +create Person1 +ArchiveCommand -> Person1 +activate Person1 + +Person1 --> ArchiveCommand +deactivate Person1 + +create Person2 +ArchiveCommand -> Person2 : createArchivedPerson("p1:Person") +activate Person2 + +Person2 --> ArchiveCommand +deactivate Person2 + +ArchiveCommand -> Model : archivePerson("p1:Person", "p2:Person") +activate Model + +Model --> ArchiveCommand +deactivate Model + +create CommandResult +ArchiveCommand -> CommandResult +activate CommandResult + +CommandResult --> ArchiveCommand +deactivate CommandResult + +ArchiveCommand --> LogicManager : r +deactivate ArchiveCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 598474a5c82..bc85397f0e1 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -14,8 +14,22 @@ UniquePersonList -right-> Person Person -up-> "*" Tag -Person *--> Name +Person *--> FirstName +Person *--> LastName +Person *--> Sex +Person *--> PayRate +Person *--> BankDetails Person *--> Phone -Person *--> Email Person *--> Address +Person *--> HoursWorked +Person *--> ArchiveStatus + +FirstName -[hidden]right-> LastName +LastName -[hidden]right-> Phone +Phone -[hidden]right-> Sex +Sex -[hidden]right-> PayRate +PayRate -[hidden]right-> Address +Address -[hidden]right-> BankDetails +BankDetails -[hidden]right-> HoursWorked +HoursWorked -[hidden]right-> ArchiveStatus @enduml diff --git a/docs/diagrams/ComponentManagers.puml b/docs/diagrams/ComponentManagers.puml index 564dd1ae32f..d39564f7d88 100644 --- a/docs/diagrams/ComponentManagers.puml +++ b/docs/diagrams/ComponentManagers.puml @@ -4,6 +4,11 @@ skinparam arrowThickness 1.1 skinparam arrowColor LOGIC_COLOR_T4 skinparam classBackgroundColor LOGIC_COLOR +package Ui as UiPackage { +Class "<>\nUi" as Ui +Class UiManager +} + package Logic as LogicPackage { Class "<>\nLogic" as Logic Class LogicManager @@ -19,13 +24,12 @@ Class "<>\nStorage" as Storage Class StorageManager } -Class HiddenOutside #FFFFFF -HiddenOutside ..> Logic - LogicManager .up.|> Logic ModelManager .up.|> Model StorageManager .up.|> Storage +UiManager .up.|> Ui LogicManager --> Model LogicManager --> Storage +UiManager ..> Logic @enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 5241e79d7da..ddb849c0d6d 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -14,10 +14,10 @@ box Model MODEL_COLOR_T1 participant "m:Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("delete 98765432") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") +LogicManager -> AddressBookParser : parseCommand("delete 98765432") activate AddressBookParser create DeleteCommandParser @@ -27,7 +27,7 @@ activate DeleteCommandParser DeleteCommandParser --> AddressBookParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +AddressBookParser -> DeleteCommandParser : parse("98765432") activate DeleteCommandParser create DeleteCommand @@ -49,7 +49,7 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute(m) activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : deletePerson(98765432) activate Model Model --> DeleteCommand diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..99445db4bb9 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -8,17 +8,25 @@ Package Model as ModelPackage <>{ Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs Class "<>\nModel" as Model +Class "<>\nSchedule" as Schedule Class AddressBook Class ModelManager Class UserPrefs +Class ScheduleManager Class UniquePersonList Class Person Class Address -Class Email -Class Name +Class FirstName +Class LastName +Class BankDetails +Class PayRate +Class Sex Class Phone +Class HoursWorked Class Tag +Class PayrollWrapper +Class ArchiveStatus Class I #FFFFFF } @@ -27,28 +35,42 @@ Class HiddenOutside #FFFFFF HiddenOutside ..> Model AddressBook .up.|> ReadOnlyAddressBook - ModelManager .up.|> Model Model .right.> ReadOnlyUserPrefs Model .left.> ReadOnlyAddressBook +Model .right.> Schedule ModelManager -left-> "1" AddressBook ModelManager -right-> "1" UserPrefs +PayrollWrapper -down-> "1" Person +ScheduleManager .up.|> Schedule +ModelManager -down-> "1" ScheduleManager UserPrefs .up.|> ReadOnlyUserPrefs AddressBook *--> "1" UniquePersonList UniquePersonList --> "~* all" Person -Person *--> Name +Person *--> FirstName +Person *--> LastName Person *--> Phone -Person *--> Email +Person *--> BankDetails +Person *--> PayRate +Person *--> Sex Person *--> Address +Person *--> HoursWorked +Person *--> ArchiveStatus Person *--> "*" Tag Person -[hidden]up--> I -UniquePersonList -[hidden]right-> I +UniquePersonList -[hidden]left-> I -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +FirstName -[hidden]right-> LastName +LastName -[hidden]right-> Phone +Phone -[hidden]right-> Sex +Sex -[hidden]right-> PayRate +PayRate -[hidden]right-> Address +Address -[hidden]right-> BankDetails +BankDetails -[hidden]right-> HoursWorked +HoursWorked -[hidden]right-> ArchiveStatus ModelManager --> "~* filtered" Person +ModelManager --> "~* filtered" PayrollWrapper @enduml diff --git a/docs/diagrams/ScheduleSequenceDiagram.puml b/docs/diagrams/ScheduleSequenceDiagram.puml new file mode 100644 index 00000000000..6ccedb8e374 --- /dev/null +++ b/docs/diagrams/ScheduleSequenceDiagram.puml @@ -0,0 +1,76 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ScheduleCommandParser" as ScheduleCommandParser LOGIC_COLOR +participant "a:ScheduleCommand" as ScheduleCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("schedule 98765432 2024-04-15") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("schedule 98765432 2024-04-15") +activate AddressBookParser + +create ScheduleCommandParser +AddressBookParser -> ScheduleCommandParser +activate ScheduleCommandParser + +ScheduleCommandParser --> AddressBookParser +deactivate ScheduleCommandParser + +AddressBookParser -> ScheduleCommandParser : parse("98765432 2024-04-15") +activate ScheduleCommandParser + +create ScheduleCommand +ScheduleCommandParser -> ScheduleCommand +activate ScheduleCommand + +ScheduleCommand --> ScheduleCommandParser : +deactivate ScheduleCommand + +ScheduleCommandParser --> AddressBookParser : a +deactivate ScheduleCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ScheduleCommandParser -[hidden]-> AddressBookParser +destroy ScheduleCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> ScheduleCommand : execute(m) +activate ScheduleCommand + +ScheduleCommand -> Model : getPersonByPhoneNumber("98765432") +activate Model + +Model --> ScheduleCommand : p1 +deactivate Model + +ScheduleCommand -> Model : addPersonToSchedule(p1, "2024-04-15") +activate Model + +Model --> ScheduleCommand +deactivate Model + +create CommandResult +ScheduleCommand -> CommandResult +activate CommandResult + +CommandResult --> ScheduleCommand +deactivate CommandResult + +ScheduleCommand --> LogicManager : r +deactivate ScheduleCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..7c34701758d 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -22,14 +22,22 @@ Class JsonAdaptedPerson Class JsonAdaptedTag } +package "Schedule Storage" #F4F6F6{ +Class "<>\nScheduleStorage" as ScheduleStorage +Class JsonScheduleStorage +Class JsonSerializableSchedule +Class JsonAdaptedScheduleDate +} + } Class HiddenOutside #FFFFFF HiddenOutside ..> Storage StorageManager .up.|> Storage -StorageManager -up-> "1" UserPrefsStorage -StorageManager -up-> "1" AddressBookStorage +StorageManager -down-> "1" UserPrefsStorage +StorageManager -down-> "1" AddressBookStorage +StorageManager -down-> "1" ScheduleStorage Storage -left-|> UserPrefsStorage Storage -right-|> AddressBookStorage @@ -40,4 +48,9 @@ JsonAddressBookStorage ..> JsonSerializableAddressBook JsonSerializableAddressBook --> "*" JsonAdaptedPerson JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonScheduleStorage .up.|> ScheduleStorage +JsonScheduleStorage ..> JsonSerializableSchedule +JsonSerializableSchedule --> "*" JsonAdaptedScheduleDate +JsonAdaptedScheduleDate --> "*" JsonAdaptedPerson + @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..f3133d44f28 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -15,6 +15,9 @@ Class PersonListPanel Class PersonCard Class StatusBarFooter Class CommandBox +Class Calendar +Class PayrollListPanel +Class PayrollCard } package Model <> { @@ -32,22 +35,29 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "0..1" PersonListPanel +MainWindow *-down-> "0..1" PayrollListPanel +MainWindow *-down-> "0..1" Calendar MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow PersonListPanel -down-> "*" PersonCard +PayrollListPanel -down-> "*" PayrollCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart PersonListPanel --|> UiPart +PayrollCard --|> UiPart +PayrollListPanel --|> UiPart PersonCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart PersonCard ..> Model +PayrollCard ..> Model +Calendar ..> Model UiManager -right-> Logic MainWindow -left-> Logic diff --git a/docs/images/1rbg.png b/docs/images/1rbg.png new file mode 100644 index 00000000000..88deafeb620 Binary files /dev/null and b/docs/images/1rbg.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 37ad06a2803..121220ed723 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/ArchiveSequenceDiagram.png b/docs/images/ArchiveSequenceDiagram.png new file mode 100644 index 00000000000..b3fdd9f70b6 Binary files /dev/null and b/docs/images/ArchiveSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 02a42e35e76..406e0d2ffbd 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/CommandBox.png b/docs/images/CommandBox.png new file mode 100644 index 00000000000..08a45c71d2c Binary files /dev/null and b/docs/images/CommandBox.png differ diff --git a/docs/images/ComponentManagers.png b/docs/images/ComponentManagers.png index ae52a35718a..8a99dc1bf63 100644 Binary files a/docs/images/ComponentManagers.png and b/docs/images/ComponentManagers.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index ac2ae217c51..fa4ebd13ca8 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/Find_UI.png b/docs/images/Find_UI.png new file mode 100644 index 00000000000..31239e90aa6 Binary files /dev/null and b/docs/images/Find_UI.png differ diff --git a/docs/images/LabelledUi.png b/docs/images/LabelledUi.png new file mode 100644 index 00000000000..ded70e751f4 Binary files /dev/null and b/docs/images/LabelledUi.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index a19fb1b4ac8..705a348706e 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/PayrollPageSuccess.png b/docs/images/PayrollPageSuccess.png new file mode 100644 index 00000000000..5aa7cf86eb3 Binary files /dev/null and b/docs/images/PayrollPageSuccess.png differ diff --git a/docs/images/Sample_UI.jpg b/docs/images/Sample_UI.jpg new file mode 100644 index 00000000000..c1d31359f70 Binary files /dev/null and b/docs/images/Sample_UI.jpg differ diff --git a/docs/images/SchedulePage.png b/docs/images/SchedulePage.png new file mode 100644 index 00000000000..15a56e7258f Binary files /dev/null and b/docs/images/SchedulePage.png differ diff --git a/docs/images/SchedulePageSuccess.png b/docs/images/SchedulePageSuccess.png new file mode 100644 index 00000000000..c763a1615b6 Binary files /dev/null and b/docs/images/SchedulePageSuccess.png differ diff --git a/docs/images/ScheduleSequenceDiagram.png b/docs/images/ScheduleSequenceDiagram.png new file mode 100644 index 00000000000..01692558abe Binary files /dev/null and b/docs/images/ScheduleSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 18fa4d0d51f..56db17a6d05 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Terminal.png b/docs/images/Terminal.png new file mode 100644 index 00000000000..988f7709564 Binary files /dev/null and b/docs/images/Terminal.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..072c1778c1a 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 11f06d68671..d04d152ae97 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UnschedulePageSuccess.png b/docs/images/UnschedulePageSuccess.png new file mode 100644 index 00000000000..fd553c93267 Binary files /dev/null and b/docs/images/UnschedulePageSuccess.png differ diff --git a/docs/images/h4ow3i.png b/docs/images/h4ow3i.png new file mode 100644 index 00000000000..a498a8d29a8 Binary files /dev/null and b/docs/images/h4ow3i.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..d70de5b5fbf 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/huekoh.png b/docs/images/huekoh.png new file mode 100644 index 00000000000..8015e66dd16 Binary files /dev/null and b/docs/images/huekoh.png differ diff --git a/docs/images/jxunze.png b/docs/images/jxunze.png new file mode 100644 index 00000000000..9ce12b6122c Binary files /dev/null and b/docs/images/jxunze.png differ diff --git a/docs/images/listall_screenshot.png b/docs/images/listall_screenshot.png new file mode 100644 index 00000000000..53bf4c062cb Binary files /dev/null and b/docs/images/listall_screenshot.png differ diff --git a/docs/images/listarchive_screenshot.png b/docs/images/listarchive_screenshot.png new file mode 100644 index 00000000000..3969ebfc6cb Binary files /dev/null and b/docs/images/listarchive_screenshot.png differ diff --git a/docs/images/listmain_screenshot.png b/docs/images/listmain_screenshot.png new file mode 100644 index 00000000000..d3d2cf83769 Binary files /dev/null and b/docs/images/listmain_screenshot.png differ diff --git a/docs/images/martinng01.png b/docs/images/martinng01.png new file mode 100644 index 00000000000..7a1be811774 Binary files /dev/null and b/docs/images/martinng01.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..0d3d0d04f3e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,24 @@ --- layout: page -title: AddressBook Level-3 +title: FnBuddy --- -[![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/AY2324S2-CS2103T-T17-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S2-CS2103T-T17-4/tp/actions) +[![codecov](https://codecov.io/gh/AY2324S2-CS2103T-T17-4/tp/graph/badge.svg?token=LPV8FKMOGM)](https://codecov.io/gh/AY2324S2-CS2103T-T17-4/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). +**FnBuddy is an innovative employee contact management application designed specifically for restaurant managers.** +It offers a user-friendly interface (both CLI and GUI) that allows you to effortlessly create, manage, +and maintain contact records for all your part-time employees. With FnBuddy, you can store essential information such as +contact details, banking information, and work schedules, ensuring efficient communication and accurate payroll +calculations. -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using FnBuddy, head over to the [_Quick Start_ section of the **User Guide**](UserGuide. + html#quick-start). +* If you are interested about developing FnBuddy, the [**Developer Guide**](DeveloperGuide.html) is a good place to + start. **Acknowledgements** diff --git a/docs/team/gabrielchua.md b/docs/team/gabrielchua.md new file mode 100644 index 00000000000..3a224b92e12 --- /dev/null +++ b/docs/team/gabrielchua.md @@ -0,0 +1,46 @@ +--- +layout: page +title: Gabriel Chua'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/huekoh.md b/docs/team/huekoh.md new file mode 100644 index 00000000000..92ef34a6ee1 --- /dev/null +++ b/docs/team/huekoh.md @@ -0,0 +1,46 @@ +--- +layout: page +title: Hue Koh's Project Portfolio Page +--- + +### Project: FnBuddy + +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/johndoe.md b/docs/team/jinxunze.md similarity index 98% rename from docs/team/johndoe.md rename to docs/team/jinxunze.md index 773a07794e2..be38dc7f9d8 100644 --- a/docs/team/johndoe.md +++ b/docs/team/jinxunze.md @@ -1,6 +1,6 @@ --- layout: page -title: John Doe's Project Portfolio Page +title: Jin Xunze's Project Portfolio Page --- ### Project: AddressBook Level 3 diff --git a/docs/team/martinng.md b/docs/team/martinng.md new file mode 100644 index 00000000000..23b06ff28ad --- /dev/null +++ b/docs/team/martinng.md @@ -0,0 +1,53 @@ +--- +layout: page +title: Martin Ng'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/teohaowei.md b/docs/team/teohaowei.md new file mode 100644 index 00000000000..cd8a27774ff --- /dev/null +++ b/docs/team/teohaowei.md @@ -0,0 +1,46 @@ +--- +layout: page +title: Teo Hao Wei's Project Portfolio Page +--- + +### Project: FnBuddy + +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/src/main/java/seedu/address/Main.java b/src/main/java/seedu/address/Main.java index ec1b7958746..f14473af111 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/seedu/address/Main.java @@ -7,17 +7,17 @@ /** * The main entry point to the application. - * + *

* This is a workaround for the following error when MainApp is made the * entry point of the application: - * - * Error: JavaFX runtime components are missing, and are required to run this application - * + *

+ * Error: JavaFX runtime components are missing, and are required to run this application + *

* The reason is that MainApp extends Application. In that case, the * LauncherHelper will check for the javafx.graphics module to be present * as a named module. We don't use JavaFX via the module system so it can't * find the javafx.graphics module, and so the launch is aborted. - * + *

* By having a separate main class (Main) that doesn't extend Application * to be the entry point of the application, we avoid this issue. */ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 3d6bd06d5af..c3f7a40444c 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -21,10 +21,14 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; +import seedu.address.model.schedule.Schedule; +import seedu.address.model.schedule.ScheduleManager; import seedu.address.model.util.SampleDataUtil; import seedu.address.storage.AddressBookStorage; import seedu.address.storage.JsonAddressBookStorage; +import seedu.address.storage.JsonScheduleStorage; import seedu.address.storage.JsonUserPrefsStorage; +import seedu.address.storage.ScheduleStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; @@ -36,7 +40,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 2, true); + public static final Version VERSION = new Version(1, 2, 1, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -48,7 +52,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing FnBuddy ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -58,7 +62,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + ScheduleStorage scheduleStorage = new JsonScheduleStorage(userPrefs.getScheduleFilePath()); + storage = new StorageManager(addressBookStorage, userPrefsStorage, scheduleStorage); model = initModelManager(storage, userPrefs); @@ -76,21 +81,35 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { logger.info("Using data file : " + storage.getAddressBookFilePath()); Optional addressBookOptional; - ReadOnlyAddressBook initialData; + ReadOnlyAddressBook abInitialData; try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { logger.info("Creating a new data file " + storage.getAddressBookFilePath() - + " populated with a sample AddressBook."); + + " populated with a sample AddressBook."); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + abInitialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); } catch (DataLoadingException e) { logger.warning("Data file at " + storage.getAddressBookFilePath() + " could not be loaded." + " Will be starting with an empty AddressBook."); - initialData = new AddressBook(); + abInitialData = new AddressBook(); + } + Optional scheduleOptional; + Schedule scheduleInitialData; + try { + scheduleOptional = storage.readSchedule(); + if (!scheduleOptional.isPresent()) { + logger.info("Creating a new data file " + storage.getScheduleFilePath() + + " populated with a sample Schedule."); + } + scheduleInitialData = scheduleOptional.orElseGet(SampleDataUtil::getSampleSchedule); + } catch (DataLoadingException e) { + logger.warning("Data file at " + storage.getScheduleFilePath() + " could not be loaded." + + " Will be starting with an empty Schedule."); + scheduleInitialData = new ScheduleManager(); } - return new ModelManager(initialData, userPrefs); + return new ModelManager(abInitialData, userPrefs, scheduleInitialData); } private void initLogging(Config config) { @@ -123,7 +142,7 @@ protected Config initConfig(Path configFilePath) { initializedConfig = configOptional.orElse(new Config()); } catch (DataLoadingException e) { logger.warning("Config file at " + configFilePathUsed + " could not be loaded." - + " Using default config properties."); + + " Using default config properties."); initializedConfig = new Config(); } @@ -154,7 +173,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { initializedPrefs = prefsOptional.orElse(new UserPrefs()); } catch (DataLoadingException e) { logger.warning("Preference file at " + prefsFilePath + " could not be loaded." - + " Using default preferences."); + + " Using default preferences."); initializedPrefs = new UserPrefs(); } @@ -176,7 +195,7 @@ public void start(Stage primaryStage) { @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping FnBuddy ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java index a97a86ee8d7..56286d22adf 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/address/commons/core/GuiSettings.java @@ -12,8 +12,8 @@ */ public class GuiSettings implements Serializable { - private static final double DEFAULT_HEIGHT = 600; - private static final double DEFAULT_WIDTH = 740; + private static final double DEFAULT_HEIGHT = 420; + private static final double DEFAULT_WIDTH = 650; private final double windowWidth; private final double windowHeight; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 8cf8e15a0f0..60bf6145139 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -102,5 +102,4 @@ private static void setBaseLogger() { } } - } diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/address/commons/util/JsonUtil.java index 100cb16c395..927723785a5 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/address/commons/util/JsonUtil.java @@ -31,20 +31,19 @@ public class JsonUtil { private static final Logger logger = LogsCenter.getLogger(JsonUtil.class); private static ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules() - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) - .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) - .registerModule(new SimpleModule("SimpleModule") - .addSerializer(Level.class, new ToStringSerializer()) - .addDeserializer(Level.class, new LevelDeserializer(Level.class))); + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) + .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + .registerModule(new SimpleModule("SimpleModule") + .addSerializer(Level.class, new ToStringSerializer()) + .addDeserializer(Level.class, new LevelDeserializer(Level.class))); static void serializeObjectToJsonFile(Path jsonFile, T objectToSerialize) throws IOException { FileUtil.writeToFile(jsonFile, toJsonString(objectToSerialize)); } - static T deserializeObjectFromJsonFile(Path jsonFile, Class classOfObjectToDeserialize) - throws IOException { + static T deserializeObjectFromJsonFile(Path jsonFile, Class classOfObjectToDeserialize) throws IOException { return fromJsonString(FileUtil.readFromFile(jsonFile), classOfObjectToDeserialize); } diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..aee05ca26a6 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -1,6 +1,7 @@ package seedu.address.logic; import java.nio.file.Path; +import java.util.Set; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; @@ -8,7 +9,9 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.person.PayrollWrapper; import seedu.address.model.person.Person; +import seedu.address.model.schedule.ScheduleDate; /** * API of the Logic component @@ -30,9 +33,17 @@ public interface Logic { */ ReadOnlyAddressBook getAddressBook(); + Set getScheduleDates(); + /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the list of payrolls */ + ObservableList getPayrollList(); + + /** Returns an unmodifiable view of the list of unarchived persons */ + ObservableList getFilteredUnarchivedPersonList(); + /** * Returns the user prefs' address book file path. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 5aa3b91c7d0..044b033b995 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.nio.file.AccessDeniedException; import java.nio.file.Path; +import java.util.Set; import java.util.logging.Logger; import javafx.collections.ObservableList; @@ -15,7 +16,9 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.person.PayrollWrapper; import seedu.address.model.person.Person; +import seedu.address.model.schedule.ScheduleDate; import seedu.address.storage.Storage; /** @@ -52,6 +55,7 @@ public CommandResult execute(String commandText) throws CommandException, ParseE try { storage.saveAddressBook(model.getAddressBook()); + storage.saveSchedule(model.getSchedule()); } catch (AccessDeniedException e) { throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e); } catch (IOException ioe) { @@ -66,11 +70,26 @@ public ReadOnlyAddressBook getAddressBook() { return model.getAddressBook(); } + @Override + public Set getScheduleDates() { + return model.getScheduleDates(); + } + + @Override + public ObservableList getPayrollList() { + return model.getPayrollList(); + } + @Override public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getFilteredUnarchivedPersonList() { + return model.getFilteredUnarchivedPersonList(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..3e590de9c4f 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -14,10 +14,11 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_PERSON_NOT_FOUND = "The person does not exist in the address book"; + public static final String MESSAGE_PERSON_NOT_ARCHIVED = "The person does not exist in the archives"; public static final String MESSAGE_DUPLICATE_FIELDS = - "Multiple values specified for the following single-valued field(s): "; + "Multiple values specified for the following single-valued field(s): "; /** * Returns an error message indicating the duplicate prefixes. @@ -39,10 +40,14 @@ public static String format(Person person) { builder.append(person.getName()) .append("; Phone: ") .append(person.getPhone()) - .append("; Email: ") - .append(person.getEmail()) + .append("; Sex: ") + .append(person.getSex()) + .append("; Pay Rate: ") + .append(person.getPayRate()) .append("; Address: ") .append(person.getAddress()) + .append("; Bank Details: ") + .append(person.getBankDetails()) .append("; Tags: "); person.getTags().forEach(builder::append); return builder.toString(); diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 5d7185a9680..fb5c067abee 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -2,9 +2,12 @@ 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_BANKDETAILS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FIRSTNAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LASTNAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SEX; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import seedu.address.commons.util.ToStringBuilder; @@ -13,6 +16,7 @@ import seedu.address.model.Model; import seedu.address.model.person.Person; + /** * Adds a person to the address book. */ @@ -21,23 +25,28 @@ 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"; + + "Parameters: " + + PREFIX_FIRSTNAME + "FIRST NAME " + + PREFIX_LASTNAME + "LAST NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_SEX + "SEX " + + PREFIX_PAYRATE + "PAY RATE " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_BANKDETAILS + "BANK DETAILS " + + PREFIX_TAG + "TAG\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_FIRSTNAME + "John " + + PREFIX_LASTNAME + "Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_SEX + "m " + + PREFIX_PAYRATE + "14 " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_BANKDETAILS + "posb 123456789 " + + 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"; - + public static final String MESSAGE_DUPLICATE_PERSON = "This phone number already exists in the address book. " + + "Every contact must have a unique phone number."; private final Person toAdd; /** @@ -78,7 +87,7 @@ public boolean equals(Object other) { @Override public String toString() { return new ToStringBuilder(this) - .add("toAdd", toAdd) - .toString(); + .add("toAdd", toAdd) + .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/ArchiveCommand.java b/src/main/java/seedu/address/logic/commands/ArchiveCommand.java new file mode 100644 index 00000000000..625da906b57 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ArchiveCommand.java @@ -0,0 +1,113 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.AddCommand.MESSAGE_DUPLICATE_PERSON; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_UNARCHIVED_PERSONS; + +import java.util.List; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.ArchiveStatus; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; + +/** + * Archives an employee from the contact book into the archive book. + */ +public class ArchiveCommand extends Command { + + public static final String COMMAND_WORD = "archive"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Archives a contact. \n" + + "Parameters: archive [EXISTING PHONE NUMBER] \n" + + "Example: " + COMMAND_WORD + " 98765432"; + + public static final String MESSAGE_ARCHIVE_PERSON_SUCCESS = "Archived Person: %1$s"; + + private final Phone targetPhone; + + public ArchiveCommand(Phone targetPhone) { + this.targetPhone = targetPhone; + } + + /** + * Creates an archived person based on the person to archive. + * + * @param personToArchive Person to archive. + * @return Archived person. + */ + private static Person createArchivedPerson(Person personToArchive) { + assert personToArchive != null; + + ArchiveStatus updatedArchiveStatus = new ArchiveStatus(true); + + return new Person(personToArchive.getFirstName(), personToArchive.getLastName(), personToArchive.getPhone(), + personToArchive.getSex(), personToArchive.getPayRate(), personToArchive.getAddress(), + personToArchive.getBankDetails(), personToArchive.getWorkHours(), personToArchive.getTags(), + updatedArchiveStatus); + } + + /** + * Archives a person from the contact book. + * + * @param model {@code Model} which the command should operate on. + * @return A command result that indicates the person has been archived. + * @throws CommandException If the person does not exist in the contact book. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredUnarchivedPersonList(); + boolean exists = false; + Person personToArchive = null; + for (Person person : lastShownList) { + if (person.getPhone().equals(targetPhone)) { + exists = true; + personToArchive = person; + break; + } + } + if (!exists) { + throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); + } + + Person archivedPerson = createArchivedPerson(personToArchive); + + if (!personToArchive.isSamePerson(archivedPerson) && model.hasPerson(archivedPerson)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.archivePerson(personToArchive, archivedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_UNARCHIVED_PERSONS); + return new CommandResult(String.format(MESSAGE_ARCHIVE_PERSON_SUCCESS, Messages.format(personToArchive))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ArchiveCommand)) { + return false; + } + + // state check + ArchiveCommand e = (ArchiveCommand) other; + return targetPhone.equals(e.targetPhone); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetPhone", targetPhone) + .toString(); + } + + +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..453ad5973d3 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -4,6 +4,7 @@ import seedu.address.model.AddressBook; import seedu.address.model.Model; +import seedu.address.model.schedule.ScheduleManager; /** * Clears the address book. @@ -18,6 +19,7 @@ public class ClearCommand extends Command { public CommandResult execute(Model model) { requireNonNull(model); model.setAddressBook(new AddressBook()); + model.setSchedule(new ScheduleManager()); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 249b6072d0d..412fec0a704 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -19,13 +19,34 @@ public class CommandResult { /** The application should exit. */ private final boolean exit; + /** The application should show contacts. */ + private final boolean showContacts; + + /** The application should show schedule. */ + private final boolean showSchedule; + + /** The application should show payroll. */ + private final boolean showPayroll; + /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, + boolean showContacts, boolean showSchedule, boolean showPayroll) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + this.showContacts = showContacts; + this.showSchedule = showSchedule; + this.showPayroll = showPayroll; + } + + /** + * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, + * {@code showHelp} and {@code exit} fields, and other fields set to their default value. + */ + public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + this(feedbackToUser, showHelp, exit, false, false, false); } /** @@ -33,7 +54,7 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, false, false, true, false, false); } public String getFeedbackToUser() { @@ -48,6 +69,18 @@ public boolean isExit() { return exit; } + public boolean isShowContacts() { + return showContacts; + } + + public boolean isShowSchedule() { + return showSchedule; + } + + public boolean isShowPayroll() { + return showPayroll; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -62,12 +95,14 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && exit == otherCommandResult.exit + && showContacts == otherCommandResult.showContacts + && showSchedule == otherCommandResult.showSchedule; } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, showHelp, exit, showContacts, showSchedule, showPayroll); } @Override @@ -76,6 +111,8 @@ public String toString() { .add("feedbackToUser", feedbackToUser) .add("showHelp", showHelp) .add("exit", exit) + .add("showContacts", showContacts) + .add("showSchedule", showSchedule) .toString(); } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 1135ac19b74..734180ec06b 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -4,12 +4,12 @@ import java.util.List; -import seedu.address.commons.core.index.Index; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; /** * Deletes a person identified using it's displayed index from the address book. @@ -19,28 +19,34 @@ 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"; + + ": Deletes the person identified by the phone number.\n" + + "Parameters: PHONE NUMBER\n" + + "Example: " + COMMAND_WORD + " 89348843"; public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - private final Index targetIndex; + private final Phone targetPhone; - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; + public DeleteCommand(Phone targetPhone) { + this.targetPhone = targetPhone; } @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); + boolean exists = false; + Person personToDelete = null; + for (Person person : lastShownList) { + if (person.getPhone().equals(targetPhone)) { + exists = true; + personToDelete = person; + break; + } + } + if (!exists) { + throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); model.deletePerson(personToDelete); return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); } @@ -57,13 +63,13 @@ public boolean equals(Object other) { } DeleteCommand otherDeleteCommand = (DeleteCommand) other; - return targetIndex.equals(otherDeleteCommand.targetIndex); + return targetPhone.equals(otherDeleteCommand.targetPhone); } @Override public String toString() { return new ToStringBuilder(this) - .add("targetIndex", targetIndex) + .add("targetPhone", targetPhone.toString()) .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 4b581c7331e..98778da3385 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -2,11 +2,13 @@ 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_BANKDETAILS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FIRSTNAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LASTNAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SEX; 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; @@ -15,68 +17,104 @@ import java.util.Optional; import java.util.Set; -import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.person.Address; -import seedu.address.model.person.Email; +import seedu.address.model.person.ArchiveStatus; +import seedu.address.model.person.BankDetails; import seedu.address.model.person.Name; +import seedu.address.model.person.PayRate; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Sex; +import seedu.address.model.person.WorkHours; 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_USAGE = COMMAND_WORD + ": Edits the details of the person" + + " identified " + + "by the phone number. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: Phone number" + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_TAG + "TAG] " + + "[" + PREFIX_FIRSTNAME + "FIRST NAME] " + + "[" + PREFIX_LASTNAME + "LAST NAME] " + + "[" + PREFIX_PAYRATE + "PAY RATE] " + + "[" + PREFIX_BANKDETAILS + "BANK DETAILS] " + + "[" + PREFIX_SEX + "SEX] " + + "Example: " + COMMAND_WORD + " 85789476 " + + PREFIX_PHONE + "91234567 "; public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_DUPLICATE_PERSON = "This phone number already exists in the address book. " + + "Every contact must have a unique phone number."; - private final Index index; + private final Phone number; private final EditPersonDescriptor editPersonDescriptor; /** - * @param index of the person in the filtered person list to edit + * @param phoneNumber 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); + public EditCommand(Phone phoneNumber, EditPersonDescriptor editPersonDescriptor) { + requireNonNull(phoneNumber); requireNonNull(editPersonDescriptor); - this.index = index; + this.number = phoneNumber; this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); } + /** + * 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 updatedFirstName = editPersonDescriptor.getFirstName().orElse(personToEdit.getFirstName()); + Name updatedLastName = editPersonDescriptor.getLastName().orElse(personToEdit.getLastName()); + Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); + Sex updatedSex = editPersonDescriptor.getSex().orElse(personToEdit.getSex()); + PayRate updatedPayRate = editPersonDescriptor.getPayRate() + .orElse(personToEdit.getPayRate()); + Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + BankDetails updatedBankDetails = editPersonDescriptor.getBankDetails().orElse(personToEdit.getBankDetails()); + WorkHours updatedWorkHours = editPersonDescriptor.getHoursWorked().orElse(personToEdit.getWorkHours()); + Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); + ArchiveStatus archiveStatus = personToEdit.getArchiveStatus(); + + return new Person(updatedFirstName, updatedLastName, updatedPhone, updatedSex, updatedPayRate, + updatedAddress, updatedBankDetails, updatedWorkHours, updatedTags, archiveStatus); + } + @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); + boolean exists = false; + Person personToEdit = null; + for (Person person : lastShownList) { + if (person.getPhone().equals(number)) { + exists = true; + personToEdit = person; + break; + } + } + if (!exists) { + throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); } - Person personToEdit = lastShownList.get(index.getZeroBased()); Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { @@ -84,26 +122,9 @@ public CommandResult execute(Model model) throws CommandException { } model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(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) { if (other == this) { @@ -116,16 +137,16 @@ public boolean equals(Object other) { } EditCommand otherEditCommand = (EditCommand) other; - return index.equals(otherEditCommand.index) - && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor); + return number.equals(otherEditCommand.number) + && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor); } @Override public String toString() { return new ToStringBuilder(this) - .add("index", index) - .add("editPersonDescriptor", editPersonDescriptor) - .toString(); + .add("index", number) + .add("editPersonDescriptor", editPersonDescriptor) + .toString(); } /** @@ -133,71 +154,105 @@ public String toString() { * corresponding field value of the person. */ public static class EditPersonDescriptor { - private Name name; + private Name firstName; + private Name lastName; private Phone phone; - private Email email; private Address address; private Set tags; + private Sex sex; + private PayRate payRate; + private BankDetails bankDetails; + private WorkHours hoursWorked; - public EditPersonDescriptor() {} + public EditPersonDescriptor() { + } /** * Copy constructor. * A defensive copy of {@code tags} is used internally. */ public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); + setFirstName(toCopy.firstName); + setLastName(toCopy.lastName); setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); setTags(toCopy.tags); + setPayRate(toCopy.payRate); + setSex(toCopy.sex); + setAddress(toCopy.address); + setBankDetails(toCopy.bankDetails); + setHoursWorked(toCopy.hoursWorked); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(firstName, lastName, phone, address, tags, sex, payRate, + bankDetails); } - public void setName(Name name) { - this.name = name; + public Optional getFirstName() { + return Optional.ofNullable(firstName); } - public Optional getName() { - return Optional.ofNullable(name); + public void setFirstName(Name name) { + this.firstName = name; } - public void setPhone(Phone phone) { - this.phone = phone; + public Optional getLastName() { + return Optional.ofNullable(lastName); + } + + public void setLastName(Name name) { + this.lastName = name; } public Optional getPhone() { return Optional.ofNullable(phone); } - public void setEmail(Email email) { - this.email = email; + public void setPhone(Phone phone) { + this.phone = phone; } - public Optional getEmail() { - return Optional.ofNullable(email); + public Optional getSex() { + return Optional.ofNullable(sex); } - public void setAddress(Address address) { - this.address = address; + public void setSex(Sex sex) { + this.sex = sex; + } + + public Optional getBankDetails() { + return Optional.ofNullable(bankDetails); + } + + public void setBankDetails(BankDetails bankDetails) { + this.bankDetails = bankDetails; + } + + public Optional getPayRate() { + return Optional.ofNullable(payRate); + } + + public void setPayRate(PayRate payRate) { + this.payRate = payRate; } public Optional

getAddress() { return Optional.ofNullable(address); } - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + public void setAddress(Address address) { + this.address = address; + } + + public Optional getHoursWorked() { + return Optional.ofNullable(hoursWorked); + } + + public void setHoursWorked(WorkHours hoursWorked) { + this.hoursWorked = hoursWorked; } /** @@ -206,7 +261,15 @@ public void setTags(Set tags) { * Returns {@code Optional#empty()} if {@code tags} is null. */ public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + return tags != null ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + /** + * 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; } @Override @@ -221,22 +284,30 @@ public boolean equals(Object other) { } EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other; - return Objects.equals(name, otherEditPersonDescriptor.name) - && Objects.equals(phone, otherEditPersonDescriptor.phone) - && Objects.equals(email, otherEditPersonDescriptor.email) - && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); + return Objects.equals(firstName, otherEditPersonDescriptor.firstName) + && Objects.equals(lastName, otherEditPersonDescriptor.lastName) + && Objects.equals(phone, otherEditPersonDescriptor.phone) + && Objects.equals(tags, otherEditPersonDescriptor.tags) + && Objects.equals(sex, otherEditPersonDescriptor.sex) + && Objects.equals(payRate, otherEditPersonDescriptor.payRate) + && Objects.equals(address, otherEditPersonDescriptor.address) + && Objects.equals(bankDetails, otherEditPersonDescriptor.bankDetails) + && Objects.equals(hoursWorked, otherEditPersonDescriptor.hoursWorked); } @Override public String toString() { return new ToStringBuilder(this) - .add("name", name) - .add("phone", phone) - .add("email", email) - .add("address", address) - .add("tags", tags) - .toString(); + .add("firstName", firstName) + .add("lastName", lastName) + .add("phone", phone) + .add("sex", sex) + .add("payRate", payRate) + .add("address", address) + .add("bankDetails", bankDetails) + .add("hoursWorked", hoursWorked) + .add("tags", tags) + .toString(); } } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..8ace7068750 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -13,7 +13,7 @@ public class ExitCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, false, false, false); } } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 72b9eddd3a7..051b1aa9700 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -15,10 +15,10 @@ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons with names which match 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; @@ -31,7 +31,7 @@ public CommandResult execute(Model model) { requireNonNull(model); model.updateFilteredPersonList(predicate); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); } @Override @@ -52,7 +52,7 @@ public boolean equals(Object other) { @Override public String toString() { return new ToStringBuilder(this) - .add("predicate", predicate) - .toString(); + .add("predicate", predicate) + .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..f4996722342 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -16,6 +16,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false, false, false); } } diff --git a/src/main/java/seedu/address/logic/commands/HoursCommand.java b/src/main/java/seedu/address/logic/commands/HoursCommand.java new file mode 100644 index 00000000000..a871b2048f0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/HoursCommand.java @@ -0,0 +1,77 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.WorkHours; + +/** + * Adds work hours to a person in the address book. + */ +public class HoursCommand extends Command { + + public static final String COMMAND_WORD = "hours"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds work hours to a person in the address book. " + + "Parameters: \n" + + "Example: " + COMMAND_WORD + " 98765432 5"; + + public static final String MESSAGE_SUCCESS = "%1$s has worked %2$d hours this week"; + public static final String MESSAGE_PERSON_NOT_FOUND = "The person with phone number: %1$s does not exist"; + + private final Phone phoneNumber; + private final WorkHours hoursWorked; + + /** + * Creates an HoursCommand to add work hours to the person with the specified phone number. + */ + public HoursCommand(Phone phoneNumber, WorkHours hoursWorked) { + requireNonNull(phoneNumber); + this.phoneNumber = phoneNumber; + this.hoursWorked = hoursWorked; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + Person personToUpdate = model.getPersonByPhoneNumber(phoneNumber); + if (personToUpdate == null) { + throw new CommandException(String.format(MESSAGE_PERSON_NOT_FOUND, phoneNumber)); + } + + personToUpdate.setHoursWorked(hoursWorked); + model.updatePerson(personToUpdate); + + return new CommandResult(String.format(MESSAGE_SUCCESS, personToUpdate.getName(), + hoursWorked.getHoursWorked())); + + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof HoursCommand)) { + return false; + } + + HoursCommand otherCommand = (HoursCommand) other; + return phoneNumber.equals(otherCommand.phoneNumber) + && hoursWorked == otherCommand.hoursWorked; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("phoneNumber", phoneNumber) + .add("hoursWorked", hoursWorked) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..2f690173c62 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,7 +1,9 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ARCHIVED_PERSONS; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_UNARCHIVED_PERSONS; import seedu.address.model.Model; @@ -12,13 +14,34 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": List all persons in either the archived or" + + " unarchived list.\n" + + "Example: " + COMMAND_WORD + " archive \n" + + "Example: " + COMMAND_WORD + " main \n" + + "Example: " + COMMAND_WORD + " all \n"; + public static final String MESSAGE_SUCCESS = "Listed all employees"; + private final String keyword; + public ListCommand(String keyword) { + this.keyword = keyword; + } @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); + switch (keyword) { + case "archive": + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_ARCHIVED_PERSONS); + return new CommandResult(MESSAGE_SUCCESS); + case "main": + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_UNARCHIVED_PERSONS); + return new CommandResult(MESSAGE_SUCCESS); + case "all": + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(MESSAGE_SUCCESS); + default: + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(MESSAGE_USAGE); + } } } diff --git a/src/main/java/seedu/address/logic/commands/PayrollCommand.java b/src/main/java/seedu/address/logic/commands/PayrollCommand.java new file mode 100644 index 00000000000..2079c4d3440 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/PayrollCommand.java @@ -0,0 +1,49 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDDATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTDATE; + +import java.time.LocalDate; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Generates a payroll for a time period. + */ +public class PayrollCommand extends Command { + public static final String COMMAND_WORD = "payroll"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Generates a payroll for a time period.\n" + + "Parameters: " + + PREFIX_STARTDATE + "START DATE " + + PREFIX_ENDDATE + "END DATE\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_STARTDATE + "2021-01-01 " + + PREFIX_ENDDATE + "2021-01-31"; + + public static final String MESSAGE_SUCCESS = "Payroll generated for %1$s to %2$s"; + public static final String MESSAGE_FAILURE = "Payroll generation failed"; + private final LocalDate startDate; + private final LocalDate endDate; + + /** + * Creates a PayrollCommand to generate a payroll for the specified time period. + */ + public PayrollCommand(LocalDate startDate, LocalDate endDate) { + this.startDate = startDate; + this.endDate = endDate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + try { + model.generatePayroll(startDate, endDate); + return new CommandResult(String.format(MESSAGE_SUCCESS, startDate, endDate), false, false, + false, false, true); + } catch (Exception e) { + return new CommandResult(MESSAGE_FAILURE); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/ScheduleCommand.java b/src/main/java/seedu/address/logic/commands/ScheduleCommand.java new file mode 100644 index 00000000000..988d48d2831 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ScheduleCommand.java @@ -0,0 +1,77 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; + +/** + * Adds a person to the address book. + */ +public class ScheduleCommand extends Command { + + public static final String COMMAND_WORD = "schedule"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the schedule.\n" + + "Parameters: " + + "PHONE " + + "DATE in the format yyyy-mm-dd\n" + + "Example: " + COMMAND_WORD + " " + + "91234567 " + + "2021-01-01"; + + public static final String MESSAGE_SUCCESS = "Schedule: %1$s added on %2$s"; + private final Phone phoneNumber; + private final LocalDate date; + + /** + * Creates a ScheduleCommand to add the specified {@code Person} to the schedule on the specified {@code LocalDate}. + */ + public ScheduleCommand(Phone phoneNumber, LocalDate date) { + requireAllNonNull(phoneNumber, date); + this.phoneNumber = phoneNumber; + this.date = date; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Person person = model.getPersonByPhoneNumber(phoneNumber); + if (person == null) { + throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); + } + model.addPersonToSchedule(person, date); + return new CommandResult(String.format(MESSAGE_SUCCESS, person.getName().value, date), false, false, + false, true, false); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ScheduleCommand)) { + return false; + } + + ScheduleCommand otherScheduleCommand = (ScheduleCommand) other; + return phoneNumber.equals(otherScheduleCommand.phoneNumber) && date.equals(otherScheduleCommand.date); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("phoneNumber", phoneNumber) + .add("date", date) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UnarchiveCommand.java b/src/main/java/seedu/address/logic/commands/UnarchiveCommand.java new file mode 100644 index 00000000000..acec974e39e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnarchiveCommand.java @@ -0,0 +1,115 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.AddCommand.MESSAGE_DUPLICATE_PERSON; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ARCHIVED_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_UNARCHIVED_PERSONS; + +import java.util.List; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.ArchiveStatus; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; + +/** + * Archives an employee from the contact book into the archive book. + */ +public class UnarchiveCommand extends Command { + + public static final String COMMAND_WORD = "unarchive"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Archives a contact. \n" + + "Parameters: unarchive [EXISTING PHONE NUMBER] \n" + + "Example: " + COMMAND_WORD + " 98765432"; + + public static final String MESSAGE_ARCHIVE_PERSON_SUCCESS = "Un-archived Person: %1$s"; + + private final Phone targetPhone; + + public UnarchiveCommand(Phone targetPhone) { + this.targetPhone = targetPhone; + } + + /** + * Creates an archived person based on the person to archive. + * + * @param personToUnarchive Person to archive. + * @return Archived person. + */ + private static Person createUnarchivedPerson(Person personToUnarchive) { + assert personToUnarchive != null; + + ArchiveStatus updatedArchiveStatus = new ArchiveStatus(false); + + return new Person(personToUnarchive.getFirstName(), personToUnarchive.getLastName(), + personToUnarchive.getPhone(), personToUnarchive.getSex(), personToUnarchive.getPayRate(), + personToUnarchive.getAddress(), personToUnarchive.getBankDetails(), personToUnarchive.getWorkHours(), + personToUnarchive.getTags(), updatedArchiveStatus); + } + + /** + * Archives a person from the contact book. + * + * @param model {@code Model} which the command should operate on. + * @return A command result that indicates the person has been archived. + * @throws CommandException If the person does not exist in the contact book. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_ARCHIVED_PERSONS); + List archiveList = model.getFilteredPersonList(); + boolean exists = false; + Person personToUnarchive = null; + for (Person person : archiveList) { + if (person.getPhone().equals(targetPhone)) { + exists = true; + personToUnarchive = person; + break; + } + } + if (!exists) { + throw new CommandException(Messages.MESSAGE_PERSON_NOT_ARCHIVED); + } + + Person unarchivedPerson = createUnarchivedPerson(personToUnarchive); + + if (!personToUnarchive.isSamePerson(unarchivedPerson) && model.hasPerson(unarchivedPerson)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.unarchivePerson(personToUnarchive, unarchivedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_UNARCHIVED_PERSONS); + return new CommandResult(String.format(MESSAGE_ARCHIVE_PERSON_SUCCESS, Messages.format(personToUnarchive))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UnarchiveCommand)) { + return false; + } + + // state check + UnarchiveCommand e = (UnarchiveCommand) other; + return targetPhone.equals(e.targetPhone); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetPhone", targetPhone) + .toString(); + } + + +} diff --git a/src/main/java/seedu/address/logic/commands/UnscheduleCommand.java b/src/main/java/seedu/address/logic/commands/UnscheduleCommand.java new file mode 100644 index 00000000000..b68d947c202 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnscheduleCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; + +/** + * Removes a person from the schedule. + */ +public class UnscheduleCommand extends Command { + + public static final String COMMAND_WORD = "unschedule"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes a person from the schedule.\n" + + "Parameters: " + + "PHONE " + + "DATE in the format yyyy-mm-dd\n" + + "Example: " + COMMAND_WORD + " " + + "91234567 " + + "2021-01-01"; + + public static final String MESSAGE_SUCCESS = "Schedule: %1$s removed from %2$s"; + private final Phone phoneNumber; + private final LocalDate date; + + /** + * Creates an UnscheduleCommand to remove the specified {@code Person} from the schedule on the specified + * {@code LocalDate}. + */ + public UnscheduleCommand(Phone phoneNumber, LocalDate date) { + requireAllNonNull(phoneNumber, date); + this.phoneNumber = phoneNumber; + this.date = date; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Person person = model.getPersonByPhoneNumber(phoneNumber); + model.removePersonFromSchedule(person, date); + return new CommandResult(String.format(MESSAGE_SUCCESS, person.getName().value, date), false, false, + false, true, false); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UnscheduleCommand)) { + return false; + } + + UnscheduleCommand otherUnscheduleCommand = (UnscheduleCommand) other; + return phoneNumber.equals(otherUnscheduleCommand.phoneNumber) && date.equals(otherUnscheduleCommand.date); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("phoneNumber", phoneNumber) + .add("date", date) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 4ff1a97ed77..d0d32b14546 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -2,21 +2,28 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BANKDETAILS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FIRSTNAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LASTNAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SEX; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.ParserUtil.arePrefixesPresent; 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.ArchiveStatus; +import seedu.address.model.person.BankDetails; import seedu.address.model.person.Name; +import seedu.address.model.person.PayRate; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Sex; +import seedu.address.model.person.WorkHours; import seedu.address.model.tag.Tag; /** @@ -27,35 +34,39 @@ 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); + ArgumentTokenizer.tokenize(args, PREFIX_FIRSTNAME, PREFIX_LASTNAME, PREFIX_PHONE, + PREFIX_SEX, PREFIX_PAYRATE, PREFIX_ADDRESS, PREFIX_BANKDETAILS, + PREFIX_TAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { + if (!arePrefixesPresent(argMultimap, PREFIX_FIRSTNAME, PREFIX_LASTNAME, + PREFIX_PHONE, PREFIX_SEX, PREFIX_PAYRATE, PREFIX_ADDRESS) + || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_FIRSTNAME, PREFIX_LASTNAME, PREFIX_PHONE, + PREFIX_SEX, PREFIX_PAYRATE, + PREFIX_ADDRESS, PREFIX_BANKDETAILS); + Name firstName = ParserUtil.parseName(argMultimap.getValue(PREFIX_FIRSTNAME).get()); + Name lastName = ParserUtil.parseName(argMultimap.getValue(PREFIX_LASTNAME).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()); + Sex sex = ParserUtil.parseSex(argMultimap.getValue(PREFIX_SEX).get()); + PayRate payRate = + ParserUtil.parsePayRate(argMultimap.getValue(PREFIX_PAYRATE).get()); + Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).orElse("")); + BankDetails bankDetails = + ParserUtil.parseBankDetails(argMultimap.getValue(PREFIX_BANKDETAILS).orElse("")); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(firstName, lastName, phone, sex, payRate, address, + bankDetails, new WorkHours(), tagList, new ArchiveStatus(false)); return new AddCommand(person); } - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 3149ee07e0b..a7f268050d7 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -9,6 +9,7 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.ArchiveCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; @@ -16,7 +17,12 @@ import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.HoursCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.PayrollCommand; +import seedu.address.logic.commands.ScheduleCommand; +import seedu.address.logic.commands.UnarchiveCommand; +import seedu.address.logic.commands.UnscheduleCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -38,7 +44,7 @@ public class AddressBookParser { * @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()); + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput); if (!matcher.matches()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); } @@ -69,7 +75,7 @@ public Command parseCommand(String userInput) throws ParseException { return new FindCommandParser().parse(arguments); case ListCommand.COMMAND_WORD: - return new ListCommand(); + return new ListCommandParser().parse(arguments); case ExitCommand.COMMAND_WORD: return new ExitCommand(); @@ -77,10 +83,27 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case HoursCommand.COMMAND_WORD: + return new HoursCommandParser().parse(arguments); + + case ScheduleCommand.COMMAND_WORD: + return new ScheduleCommandParser().parse(arguments); + + case UnscheduleCommand.COMMAND_WORD: + return new UnscheduleCommandParser().parse(arguments); + + case ArchiveCommand.COMMAND_WORD: + return new ArchiveCommandParser().parse(arguments); + + case UnarchiveCommand.COMMAND_WORD: + return new UnarchiveCommandParser().parse(arguments); + + case PayrollCommand.COMMAND_WORD: + return new PayrollCommandParser().parse(arguments); + default: logger.finer("This user input caused a ParseException: " + userInput); throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } } - } diff --git a/src/main/java/seedu/address/logic/parser/ArchiveCommandParser.java b/src/main/java/seedu/address/logic/parser/ArchiveCommandParser.java new file mode 100644 index 00000000000..1e6bd025ba9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ArchiveCommandParser.java @@ -0,0 +1,28 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ArchiveCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new ArchiveCommand object + */ +public class ArchiveCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ArchiveCommand + * and returns an ArchiveCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ArchiveCommand parse(String args) throws ParseException { + try { + Phone number = ParserUtil.parsePhone(args); + return new ArchiveCommand(number); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ArchiveCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..3be8e89059d 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java @@ -7,11 +7,11 @@ /** * Tokenizes arguments string of the form: {@code preamble value value ...}
- * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
+ * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
* 1. An argument's value can be an empty string e.g. the value of {@code k/} in the above example.
* 2. Leading and trailing whitespaces of an argument value will be discarded.
* 3. An argument may be repeated and all its values will be accumulated e.g. the value of {@code t/} - * in the above example.
+ * in the above example.
*/ public class ArgumentTokenizer { @@ -21,7 +21,7 @@ public class ArgumentTokenizer { * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixes Prefixes to tokenize the arguments string with - * @return ArgumentMultimap object that maps prefixes to their arguments + * @return ArgumentMultimap object that maps prefixes to their arguments */ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { List positions = findAllPrefixPositions(argsString, prefixes); @@ -33,7 +33,7 @@ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixes Prefixes to find in the arguments string - * @return List of zero-based prefix positions in the given arguments string + * @return List of zero-based prefix positions in the given arguments string */ private static List findAllPrefixPositions(String argsString, Prefix... prefixes) { return Arrays.stream(prefixes) @@ -62,7 +62,7 @@ private static List findPrefixPositions(String argsString, Prefi * {@code argsString} starting from index {@code fromIndex}. An occurrence * is valid if there is a whitespace before {@code prefix}. Returns -1 if no * such occurrence can be found. - * + *

* E.g if {@code argsString} = "e/hip/900", {@code prefix} = "p/" and * {@code fromIndex} = 0, this method returns -1 as there are no valid * occurrences of "p/" with whitespace before it. However, if @@ -82,7 +82,7 @@ private static int findPrefixPosition(String argsString, String prefix, int from * * @param argsString Arguments string of the form: {@code preamble value value ...} * @param prefixPositions Zero-based positions of all prefixes in {@code argsString} - * @return ArgumentMultimap object that maps prefixes to their arguments + * @return ArgumentMultimap object that maps prefixes to their arguments */ private static ArgumentMultimap extractArguments(String argsString, List prefixPositions) { @@ -114,8 +114,8 @@ private static ArgumentMultimap extractArguments(String argsString, List { */ public DeleteCommand parse(String args) throws ParseException { try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + Phone number = ParserUtil.parsePhone(args); + return new DeleteCommand(number); } 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 index 46b3309a78b..90dbc1da2aa 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -3,9 +3,12 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BANKDETAILS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FIRSTNAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LASTNAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PAYRATE; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SEX; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Collection; @@ -13,10 +16,9 @@ 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.person.Phone; import seedu.address.model.tag.Tag; /** @@ -27,44 +29,58 @@ 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); + ArgumentTokenizer.tokenize(args, PREFIX_FIRSTNAME, PREFIX_LASTNAME, PREFIX_PHONE, + PREFIX_ADDRESS, PREFIX_TAG, PREFIX_SEX, PREFIX_PAYRATE, PREFIX_BANKDETAILS); - Index index; + Phone number; try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); + number = ParserUtil.parsePhone(argMultimap.getPreamble()); } catch (ParseException pe) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_FIRSTNAME, PREFIX_LASTNAME, PREFIX_PHONE, PREFIX_SEX, + PREFIX_PAYRATE, PREFIX_BANKDETAILS, PREFIX_ADDRESS); - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + EditCommand.EditPersonDescriptor editPersonDescriptor = new EditCommand.EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + if (argMultimap.getValue(PREFIX_FIRSTNAME).isPresent()) { + editPersonDescriptor.setFirstName(ParserUtil.parseName(argMultimap.getValue(PREFIX_FIRSTNAME).get())); + } + if (argMultimap.getValue(PREFIX_LASTNAME).isPresent()) { + editPersonDescriptor.setLastName(ParserUtil.parseName(argMultimap.getValue(PREFIX_LASTNAME).get())); } if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } + if (argMultimap.getValue(PREFIX_SEX).isPresent()) { + editPersonDescriptor.setSex(ParserUtil.parseSex(argMultimap.getValue(PREFIX_SEX).get())); + } + if (argMultimap.getValue(PREFIX_PAYRATE).isPresent()) { + editPersonDescriptor.setPayRate(ParserUtil + .parsePayRate(argMultimap.getValue(PREFIX_PAYRATE).get())); + } + if (argMultimap.getValue(PREFIX_BANKDETAILS).isPresent()) { + editPersonDescriptor.setBankDetails(ParserUtil + .parseBankDetails(argMultimap.getValue(PREFIX_BANKDETAILS).get())); + } parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); + return new EditCommand(number, editPersonDescriptor); } /** diff --git a/src/main/java/seedu/address/logic/parser/HoursCommandParser.java b/src/main/java/seedu/address/logic/parser/HoursCommandParser.java new file mode 100644 index 00000000000..e590d57580d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/HoursCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.HoursCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Phone; +import seedu.address.model.person.WorkHours; + +/** + * Parses input arguments and creates a new HoursCommand object + */ +public class HoursCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the HoursCommand + * and returns a HoursCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public HoursCommand parse(String args) throws ParseException { + String[] tokens = args.trim().split("\\s+"); + if (tokens.length != 2) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HoursCommand.MESSAGE_USAGE)); + } + Phone phoneNumber = ParserUtil.parsePhone(tokens[0]); + WorkHours hoursWorked; + hoursWorked = ParserUtil.parseWorkHours(tokens[1]); + return new HoursCommand(phoneNumber, hoursWorked); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ListCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCommandParser.java new file mode 100644 index 00000000000..159515f1df1 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new ListCommand object + */ +public class ListCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the ListCommand + * and returns a ListCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ListCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + + String keyword = args.trim(); + + return new ListCommand(keyword); + } + + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..4d139365a82 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,17 +2,22 @@ import static java.util.Objects.requireNonNull; +import java.time.LocalDate; import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.stream.Stream; 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.BankDetails; import seedu.address.model.person.Name; +import seedu.address.model.person.PayRate; import seedu.address.model.person.Phone; +import seedu.address.model.person.Sex; +import seedu.address.model.person.WorkHours; import seedu.address.model.tag.Tag; /** @@ -25,6 +30,7 @@ public class ParserUtil { /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { @@ -65,6 +71,41 @@ public static Phone parsePhone(String phone) throws ParseException { return new Phone(trimmedPhone); } + /** + * Parses a {@code String sex} into a {@code Sex}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code sex} is invalid. + */ + public static Sex parseSex(String sex) throws ParseException { + requireNonNull(sex); + String trimmedSex = sex.trim(); + if (!Sex.isValidSex(trimmedSex)) { + throw new ParseException(Sex.MESSAGE_CONSTRAINTS); + } + return new Sex(trimmedSex); + } + + /** + * Parses a {@code String employmentType} into a {@code EmploymentType}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code employmentType} is invalid. + */ + public static PayRate parsePayRate(String payRate) throws ParseException { + requireNonNull(payRate); + String trimmedPayRate = payRate.trim(); + try { + double rate = Double.parseDouble(trimmedPayRate); + if (rate <= 0) { + throw new ParseException("Pay rate cannot be zero or negative"); + } + return new PayRate(rate); + } catch (NumberFormatException e) { + throw new ParseException(PayRate.MESSAGE_CONSTRAINTS); + } + } + /** * Parses a {@code String address} into an {@code Address}. * Leading and trailing whitespaces will be trimmed. @@ -81,18 +122,18 @@ public static Address parseAddress(String address) throws ParseException { } /** - * Parses a {@code String email} into an {@code Email}. + * Parses a {@code String bankDetails} into an {@code BankDetails}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code email} is invalid. + * @throws ParseException if the given {@code bankDetails} is invalid. */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); + public static BankDetails parseBankDetails(String bankDetails) throws ParseException { + requireNonNull(bankDetails); + String trimmedBankDetails = bankDetails.trim(); + if (!BankDetails.isValidBankAccount(trimmedBankDetails)) { + throw new ParseException(BankDetails.MESSAGE_CONSTRAINTS); } - return new Email(trimmedEmail); + return new BankDetails(trimmedBankDetails); } /** @@ -121,4 +162,50 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a string representation of work hours into a WorkHours object. + * + * @param workHours A string representing the number of work hours. + * @return A WorkHours object representing the parsed work hours. + * @throws ParseException if the work hours string is invalid or cannot be parsed. + */ + public static WorkHours parseWorkHours(String workHours) throws ParseException { + requireNonNull(workHours); + String trimmedWorkHours = workHours.trim(); + try { + int hours = Integer.parseInt(trimmedWorkHours); + if (hours < 0) { + throw new ParseException("Work hours cannot be negative"); + } + return new WorkHours(hours); + } catch (NumberFormatException e) { + throw new ParseException(WorkHours.MESSAGE_CONSTRAINTS); + } + } + + /** + * Parses a string representation of a date into a LocalDate object. + * + * @param date A string representing a date. + * @return A LocalDate object representing the parsed date. + * @throws ParseException if the date string is invalid or cannot be parsed. + */ + public static LocalDate parseDate(String date) throws ParseException { + requireNonNull(date); + String trimmedDate = date.trim(); + try { + return LocalDate.parse(trimmedDate); + } catch (Exception e) { + throw new ParseException("Invalid date format. Please enter a date in the format yyyy-mm-dd"); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + public static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } } diff --git a/src/main/java/seedu/address/logic/parser/PayrollCommandParser.java b/src/main/java/seedu/address/logic/parser/PayrollCommandParser.java new file mode 100644 index 00000000000..6f075720c7f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/PayrollCommandParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDDATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTDATE; +import static seedu.address.logic.parser.ParserUtil.arePrefixesPresent; + +import java.time.LocalDate; + +import seedu.address.logic.commands.PayrollCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new PayrollCommand object + */ +public class PayrollCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the PayrollCommand + * and returns a PayrollCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public PayrollCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_STARTDATE, PREFIX_ENDDATE); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_STARTDATE, PREFIX_ENDDATE); + if (!arePrefixesPresent(argMultimap, PREFIX_STARTDATE, PREFIX_ENDDATE)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, PayrollCommand.MESSAGE_USAGE)); + } + LocalDate startDate = ParserUtil.parseDate(argMultimap.getValue(PREFIX_STARTDATE).get()); + LocalDate endDate = ParserUtil.parseDate(argMultimap.getValue(PREFIX_ENDDATE).get()); + if (startDate.isAfter(endDate)) { + throw new ParseException("Start date cannot be after end date"); + } + return new PayrollCommand(startDate, endDate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ScheduleCommandParser.java b/src/main/java/seedu/address/logic/parser/ScheduleCommandParser.java new file mode 100644 index 00000000000..6f3cacc29e8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ScheduleCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.time.LocalDate; + +import seedu.address.logic.commands.ScheduleCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new ScheduleCommand object + */ +public class ScheduleCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ScheduleCommand + * and returns a ScheduleCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ScheduleCommand parse(String args) throws ParseException { + String[] tokens = args.trim().split("\\s+"); + if (tokens.length != 2) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ScheduleCommand.MESSAGE_USAGE)); + } + Phone phoneNumber = ParserUtil.parsePhone(tokens[0]); + LocalDate date = ParserUtil.parseDate(tokens[1]); + return new ScheduleCommand(phoneNumber, date); + } +} diff --git a/src/main/java/seedu/address/logic/parser/UnarchiveCommandParser.java b/src/main/java/seedu/address/logic/parser/UnarchiveCommandParser.java new file mode 100644 index 00000000000..1f5b3a9b0de --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnarchiveCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.UnarchiveCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new ArchiveCommand object + */ +public class UnarchiveCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ArchiveCommand + * and returns an ArchiveCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UnarchiveCommand parse(String args) throws ParseException { + try { + Phone number = ParserUtil.parsePhone(args); + return new UnarchiveCommand(number); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnarchiveCommand.MESSAGE_USAGE), pe); + } + } +} + diff --git a/src/main/java/seedu/address/logic/parser/UnscheduleCommandParser.java b/src/main/java/seedu/address/logic/parser/UnscheduleCommandParser.java new file mode 100644 index 00000000000..e282ca1b5ce --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnscheduleCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.time.LocalDate; + +import seedu.address.logic.commands.UnscheduleCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Phone; + +/** + * Parses input arguments and creates a new UnscheduleCommand object + */ +public class UnscheduleCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ScheduleCommand + * and returns a ScheduleCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public UnscheduleCommand parse(String args) throws ParseException { + String[] tokens = args.trim().split("\\s+"); + if (tokens.length != 2) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnscheduleCommand.MESSAGE_USAGE)); + } + Phone phoneNumber = ParserUtil.parsePhone(tokens[0]); + LocalDate date = ParserUtil.parseDate(tokens[1]); + return new UnscheduleCommand(phoneNumber, date); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 73397161e84..9ac783de663 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -8,6 +8,8 @@ import seedu.address.commons.util.ToStringBuilder; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.schedule.Schedule; +import seedu.address.model.schedule.ScheduleManager; /** * Wraps all data at the address-book level @@ -16,6 +18,7 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final Schedule schedule; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -26,6 +29,7 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + schedule = new ScheduleManager(); } public AddressBook() {} @@ -94,6 +98,14 @@ public void removePerson(Person key) { persons.remove(key); } + public void archivePerson(Person target, Person archivedPerson) { + persons.archive(target, archivedPerson); + } + + public void unarchivePerson(Person target, Person unarchivedPerson) { + persons.unarchive(target, unarchivedPerson); + } + //// util methods @Override @@ -108,6 +120,10 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + public Schedule getSchedule() { + return schedule; + } + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..ee7e8c3c09f 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,11 +1,17 @@ package seedu.address.model; import java.nio.file.Path; +import java.time.LocalDate; +import java.util.Set; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.model.person.PayrollWrapper; import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.schedule.Schedule; +import seedu.address.model.schedule.ScheduleDate; /** * The API of the Model component. @@ -14,6 +20,12 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** {@code Predicate} that evaluates to true when the person is not archived */ + Predicate PREDICATE_SHOW_ALL_UNARCHIVED_PERSONS = person -> !person.getArchiveStatus().getArchiveStatus(); + + /** {@code Predicate} that evaluates to true when the person is archived */ + Predicate PREDICATE_SHOW_ALL_ARCHIVED_PERSONS = person -> person.getArchiveStatus().getArchiveStatus(); + /** * Replaces user prefs data with the data in {@code userPrefs}. */ @@ -52,6 +64,29 @@ public interface Model { /** Returns the AddressBook */ ReadOnlyAddressBook getAddressBook(); + /** + * Replaces schedule data with the data in {@code schedule}. + */ + void setSchedule(Schedule schedule); + + /** Returns the Schedule */ + Schedule getSchedule(); + + /** + * Returns the schedule dates. + */ + Set getScheduleDates(); + + /** + * Adds {@code person} on {@code date} to the {@code Schedule} in the model. + */ + void addPersonToSchedule(Person person, LocalDate date); + + /** + * Removes {@code person} on {@code date} from the {@code Schedule} in the model. + */ + void removePersonFromSchedule(Person person, LocalDate date); + /** * Returns true if a person with the same identity as {@code person} exists in the address book. */ @@ -79,9 +114,42 @@ public interface Model { /** Returns an unmodifiable view of the filtered person list */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered un-archived person list */ + ObservableList getFilteredUnarchivedPersonList(); + + /** Returns an unmodifiable view of the filtered archived person list */ + ObservableList getFilteredArchivedPersonList(); + /** * 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); + + /** + * Returns a person with the given phone number. + * Returns {@code null} if no person with the given phone number is found. + */ + Person getPersonByPhoneNumber(Phone phoneNumber); + + /** + * Updates the details of the specified person in the address book with the details of the updated person. + * @param personToUpdate The person to be updated. + */ + void updatePerson(Person personToUpdate); + + void archivePerson(Person personToArchive, Person archivedPerson); + + void unarchivePerson(Person personToUnarchive, Person unarchivedPerson); + /** + * Generates the payroll for the specified period. + * @param startDate The start date of the period. + * @param endDate The end date of the period. + */ + void generatePayroll(LocalDate startDate, LocalDate endDate); + + /** + * Returns the payroll list. + */ + ObservableList getPayrollList(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..32190fc8aa6 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,14 +4,25 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Predicate; import java.util.logging.Logger; +import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.model.person.PayrollWrapper; import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.schedule.Schedule; +import seedu.address.model.schedule.ScheduleDate; +import seedu.address.model.schedule.ScheduleManager; /** * Represents the in-memory model of the address book data. @@ -21,23 +32,29 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; + private final Schedule schedule; private final FilteredList filteredPersons; + private final ObservableList payrolls; + /** * Initializes a ModelManager with the given addressBook and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - requireAllNonNull(addressBook, userPrefs); + public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs, Schedule schedule) { + requireAllNonNull(addressBook, userPrefs, schedule); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs + + " and schedule " + schedule); this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); + this.schedule = schedule; filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + payrolls = FXCollections.observableArrayList(); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new AddressBook(), new UserPrefs(), new ScheduleManager()); } //=========== UserPrefs ================================================================================== @@ -101,16 +118,73 @@ public void deletePerson(Person target) { @Override public void addPerson(Person person) { addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + updateFilteredPersonList(PREDICATE_SHOW_ALL_UNARCHIVED_PERSONS); } @Override public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); - addressBook.setPerson(target, editedPerson); } + @Override + public void archivePerson(Person target, Person archivedPerson) { + addressBook.archivePerson(target, archivedPerson); + } + + @Override + public void unarchivePerson(Person target, Person unarchivedPerson) { + addressBook.unarchivePerson(target, unarchivedPerson); + } + + //=========== Schedule =================================================================================== + + @Override + public void setSchedule(Schedule schedule) { + this.schedule.resetData(schedule); + } + + @Override + public Schedule getSchedule() { + return schedule; + } + + @Override + public Set getScheduleDates() { + return schedule.getScheduleDates(); + } + + @Override + public void addPersonToSchedule(Person person, LocalDate date) { + requireAllNonNull(person, date); + schedule.addPerson(person, date); + } + + @Override + public void removePersonFromSchedule(Person person, LocalDate date) { + requireAllNonNull(person, date); + schedule.deletePerson(person, date); + } + + //=========== Payroll =================================================================================== + + @Override + public void generatePayroll(LocalDate startDate, LocalDate endDate) { + requireAllNonNull(startDate, endDate); + payrolls.clear(); + List newPayrolls = new ArrayList<>(); + Map hoursWorked = schedule.getHoursWorked(startDate, endDate); + for (Map.Entry entry : hoursWorked.entrySet()) { + newPayrolls.add(new PayrollWrapper(entry.getKey(), entry.getValue())); + } + payrolls.setAll(newPayrolls); + } + + @Override + public ObservableList getPayrollList() { + return payrolls; + } + //=========== Filtered Person List Accessors ============================================================= /** @@ -122,6 +196,26 @@ public ObservableList getFilteredPersonList() { return filteredPersons; } + /** + * Returns an unmodifiable view of the list of unarchived {@code Person} backed by the internal list of + * {@code addressBook} + */ + @Override + public ObservableList getFilteredUnarchivedPersonList() { + this.updateFilteredPersonList(PREDICATE_SHOW_ALL_UNARCHIVED_PERSONS); + return filteredPersons; + } + + /** + * Returns an unmodifiable view of the list of archived {@code Person} backed by the internal list of + * {@code addressBook} + */ + @Override + public ObservableList getFilteredArchivedPersonList() { + this.updateFilteredPersonList(PREDICATE_SHOW_ALL_ARCHIVED_PERSONS); + return filteredPersons; + } + @Override public void updateFilteredPersonList(Predicate predicate) { requireNonNull(predicate); @@ -145,4 +239,21 @@ public boolean equals(Object other) { && filteredPersons.equals(otherModelManager.filteredPersons); } + @Override + public Person getPersonByPhoneNumber(Phone phoneNumber) { + requireNonNull(phoneNumber); + for (Person person : addressBook.getPersonList()) { + if (person.getPhone().equals(phoneNumber)) { + return person; + } + } + return null; // Return null if the person is not found + } + + @Override + public void updatePerson(Person personToUpdate) { + requireNonNull(personToUpdate); + addressBook.setPerson(personToUpdate, personToUpdate); + } + } diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java index befd58a4c73..2de0639c53c 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java @@ -13,4 +13,7 @@ public interface ReadOnlyUserPrefs { Path getAddressBookFilePath(); + Path getScheduleFilePath(); + + Path getArchiveBookFilePath(); } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 6be655fb4c7..6153b5fe72d 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,7 +14,9 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data", "addressbook.json"); + private Path scheduleFilePath = Paths.get("data", "schedule.json"); + private Path archiveBookFilePath = Paths.get("data", "archivebook.json"); /** * Creates a {@code UserPrefs} with default values. @@ -36,6 +38,7 @@ public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setScheduleFilePath(newUserPrefs.getScheduleFilePath()); } public GuiSettings getGuiSettings() { @@ -51,11 +54,24 @@ public Path getAddressBookFilePath() { return addressBookFilePath; } + public Path getArchiveBookFilePath() { + return archiveBookFilePath; + } + public void setAddressBookFilePath(Path addressBookFilePath) { requireNonNull(addressBookFilePath); this.addressBookFilePath = addressBookFilePath; } + public Path getScheduleFilePath() { + return scheduleFilePath; + } + + public void setScheduleFilePath(Path scheduleFilePath) { + requireNonNull(scheduleFilePath); + this.scheduleFilePath = scheduleFilePath; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -80,8 +96,8 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("Gui Settings : ").append(guiSettings); + sb.append("\nLocal data file location : ").append(addressBookFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 469a2cc9a1e..14e31e0986f 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -9,13 +9,13 @@ */ public class Address { - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; + public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values"; /* * 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 static final String VALIDATION_REGEX = "^.+$"; public final String value; diff --git a/src/main/java/seedu/address/model/person/ArchiveStatus.java b/src/main/java/seedu/address/model/person/ArchiveStatus.java new file mode 100644 index 00000000000..dab00d1ba88 --- /dev/null +++ b/src/main/java/seedu/address/model/person/ArchiveStatus.java @@ -0,0 +1,48 @@ +package seedu.address.model.person; + +/** + * Represents the archive status of a person in the contact book. + */ +public class ArchiveStatus { + + public static final String MESSAGE_CONSTRAINTS = "Archive status can only be '0' for not archived or " + + "'1' for archived."; + private boolean isArchived; + + /** + * Constructor for ArchiveStatus that initializes the archive status to not archived. + */ + public ArchiveStatus(boolean isArchived) { + this.isArchived = isArchived; + } + + public boolean getArchiveStatus() { + return isArchived; + } + + /** + * Sets the archive status of a person to be archived. + */ + public void setArchiveStatusTrue() { + this.isArchived = true; + } + + /** + * Sets the archive status of a person to be not archived. + */ + public void setArchiveStatusFalse() { + this.isArchived = false; + } + + @Override + public String toString() { + return isArchived ? "Archived" : "Not Archived"; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ArchiveStatus // instanceof handles nulls + && isArchived == ((ArchiveStatus) other).isArchived); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/BankDetails.java b/src/main/java/seedu/address/model/person/BankDetails.java new file mode 100644 index 00000000000..2bb1f58f813 --- /dev/null +++ b/src/main/java/seedu/address/model/person/BankDetails.java @@ -0,0 +1,73 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's bank details in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidBankAccount(String)} + */ +public class BankDetails { + + public static final String MESSAGE_CONSTRAINTS = "Bank details should be in this format: " + + " \n" + + "Accepted banks and corresponding digit length: OCBC {7}, POSB {9}, UOB {10}, DBS {10}, " + + "Standard Chartered {10}, HSBC {any length}, Others {any length}\n" + + "Examples: -b posb 1234567890/ -b hsbc 172002492/ -b other maybank 712834957521"; + public static final String DBS_UOB_STANDARD_REGEX = "(?:dbs|uob|standard chartered)\\s+\\d{10}"; + public static final String POSB_REGEX = "posb\\s+\\d{9}"; + public static final String OCBC_REGEX = "ocbc\\s+\\d{7}"; + public static final String HSBC_REGEX = "hsbc\\s+\\d+"; + public static final String OTHERS_REGEX = "other\\s+\\w+\\s+\\d+"; + + public static final String BLANK_REGEX = "\\s*"; + + public static final String VALIDATION_REGEX = DBS_UOB_STANDARD_REGEX + "|" + + POSB_REGEX + "|" + OCBC_REGEX + "|" + HSBC_REGEX + "|" + OTHERS_REGEX + "|" + BLANK_REGEX; + + public final String value; + + /** + * Constructs an {@code BankDetails}. + * + * @param bankDetails A valid bank account. + */ + public BankDetails(String bankDetails) { + requireNonNull(bankDetails); + checkArgument(isValidBankAccount(bankDetails), MESSAGE_CONSTRAINTS); + value = bankDetails; + } + + /** + * Returns true if a given string is a valid bank account. + */ + public static boolean isValidBankAccount(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof BankDetails)) { + return false; + } + + BankDetails otherBankDetails = (BankDetails) other; + return value.equals(otherBankDetails.value); + } + + @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 index 173f15b9b00..d94cb50bde0 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -10,15 +10,15 @@ public class Name { public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "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 static final String VALIDATION_REGEX = "^(?! )[a-zA-Z0-9 ]*$"; - public final String fullName; + public final String value; /** * Constructs a {@code Name}. @@ -28,7 +28,7 @@ public class Name { public Name(String name) { requireNonNull(name); checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); - fullName = name; + value = name; } /** @@ -41,7 +41,7 @@ public static boolean isValidName(String test) { @Override public String toString() { - return fullName; + return value; } @Override @@ -56,12 +56,12 @@ public boolean equals(Object other) { } Name otherName = (Name) other; - return fullName.equals(otherName.fullName); + return value.equals(otherName.value); } @Override public int hashCode() { - return fullName.hashCode(); + return value.hashCode(); } } diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java index 62d19be2977..8a187bd5a66 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -19,7 +19,7 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().value, keyword)); } @Override diff --git a/src/main/java/seedu/address/model/person/PayRate.java b/src/main/java/seedu/address/model/person/PayRate.java new file mode 100644 index 00000000000..0ce40aa6143 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PayRate.java @@ -0,0 +1,88 @@ +package seedu.address.model.person; + +import java.util.Objects; + +/** + * Represents a person's pay rate in the contact book + * A pay rate is represented as a floating-point number. + *

+ * Pay rates should only contain numeric values. + *

+ * Guarantees: immutable + */ +public class PayRate { + + public static final String MESSAGE_CONSTRAINTS = + "Pay rate should only contain numbers"; + + public final double value; + + /** + * Constructs a {@code PayRate} with the specified pay rate value. + * + * @param payRate The pay rate value. + */ + public PayRate(double payRate) { + this.value = payRate; + } + + public double getPayRate() { + return value; + } + + /** + * Returns a string representation of the pay rate. + * + * @return A string representing the pay rate. + */ + @Override + public String toString() { + return "Pay Rate: $ " + value; + } + + /** + * Checks if the given pay rate string is valid. + * + * @param payRate The pay rate string to be checked for validity. + * @return {@code true} if the pay rate string is valid, {@code false} otherwise. + */ + public static boolean isValidPayRate(String payRate) { + if (payRate == null) { + return false; + } + try { + double rate = Double.parseDouble(payRate); + return rate > 0; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * Checks if this pay rate is equal to another object. + * + * @param other The object to compare with. + * @return {@code true} if the objects are equal, {@code false} otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof PayRate)) { + return false; + } + PayRate otherPayRate = (PayRate) other; + return value == otherPayRate.value; + } + + /** + * Returns the hash code of the pay rate. + * + * @return The hash code of the pay rate. + */ + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/src/main/java/seedu/address/model/person/PayrollWrapper.java b/src/main/java/seedu/address/model/person/PayrollWrapper.java new file mode 100644 index 00000000000..657c8e542ab --- /dev/null +++ b/src/main/java/seedu/address/model/person/PayrollWrapper.java @@ -0,0 +1,56 @@ +package seedu.address.model.person; + +/** + * Represents a Person's payroll in the address book. + */ +public class PayrollWrapper { + private final Person person; + private double hoursWorked = 0; + + + /** + * Creates a PayrollWrapper with the given {@code Person} and hours worked. + */ + public PayrollWrapper(Person person, double hoursWorked) { + this.person = person; + this.hoursWorked = hoursWorked; + } + + public String getName() { + return person.getName().toString(); + } + + public double getHoursWorked() { + return hoursWorked; + } + + public double getPay() { + return hoursWorked * person.getPayRate().value; + } + + public Person getPerson() { + return person; + } + + @Override + public int hashCode() { + return person.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof PayrollWrapper)) { + return false; + } + PayrollWrapper otherPayrollWrapper = (PayrollWrapper) other; + return otherPayrollWrapper.person.equals(this.person); + } + + @Override + public String toString() { + return person.toString() + " Hours Worked: " + hoursWorked; + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index abe8c46b535..eeee94ddd1d 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -17,42 +17,82 @@ public class Person { // Identity fields - private final Name name; + private final Name firstName; + private final Name lastName; private final Phone phone; - private final Email email; // Data fields + private final Sex sex; + private final PayRate payRate; private final Address address; + private final BankDetails bankDetails; private final Set tags = new HashSet<>(); + private WorkHours hoursWorked; + private ArchiveStatus archiveStatus; /** * 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; + public Person(Name firstName, Name lastName, Phone phone, Sex sex, + PayRate payRate, Address address, + BankDetails bankDetails, WorkHours hoursWorked, Set tags, ArchiveStatus archiveStatus) { + requireAllNonNull(firstName, lastName, phone, address, bankDetails, tags); + this.firstName = firstName; + this.lastName = lastName; this.phone = phone; - this.email = email; + this.sex = sex; + this.payRate = payRate; this.address = address; + this.bankDetails = bankDetails; this.tags.addAll(tags); + this.hoursWorked = hoursWorked; + this.archiveStatus = archiveStatus; } public Name getName() { - return name; + return new Name(firstName + " " + lastName); + } + + public Name getFirstName() { + return firstName; + } + + public Name getLastName() { + return lastName; } public Phone getPhone() { return phone; } - public Email getEmail() { - return email; + public Sex getSex() { + return sex; + } + + public PayRate getPayRate() { + return payRate; } public Address getAddress() { return address; } + public BankDetails getBankDetails() { + return bankDetails; + } + + public WorkHours getWorkHours() { + return hoursWorked; + } + + public ArchiveStatus getArchiveStatus() { + return archiveStatus; + } + + public void setHoursWorked(WorkHours hoursWorked) { + this.hoursWorked = hoursWorked; + } + /** * Returns an immutable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. @@ -71,7 +111,7 @@ public boolean isSamePerson(Person otherPerson) { } return otherPerson != null - && otherPerson.getName().equals(getName()); + && otherPerson.phone.equals(phone); } /** @@ -90,28 +130,36 @@ public boolean equals(Object other) { } Person otherPerson = (Person) other; - return name.equals(otherPerson.name) - && phone.equals(otherPerson.phone) - && email.equals(otherPerson.email) - && address.equals(otherPerson.address) - && tags.equals(otherPerson.tags); + return getName().equals(otherPerson.getName()) + && phone.equals(otherPerson.phone) + && sex.equals(otherPerson.sex) + && payRate.equals(otherPerson.payRate) + && address.equals(otherPerson.address) + && bankDetails.equals(otherPerson.bankDetails) + && tags.equals(otherPerson.tags) + && archiveStatus.equals(otherPerson.archiveStatus); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(firstName, lastName, phone, sex, payRate, address, bankDetails, + tags); } @Override public String toString() { return new ToStringBuilder(this) - .add("name", name) - .add("phone", phone) - .add("email", email) - .add("address", address) - .add("tags", tags) - .toString(); + .add("firstName", firstName) + .add("lastName", lastName) + .add("phone", phone) + .add("sex", sex) + .add("payRate", payRate) + .add("address", address) + .add("bankDetails", bankDetails) + .add("tags", tags) + .add("archiveStatus", archiveStatus) + .toString(); } } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index d733f63d739..8da75dd199e 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -11,8 +11,8 @@ public class Phone { public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; + "Phone numbers should only contain numbers, and it should be only 8 digits long"; + public static final String VALIDATION_REGEX = "^\\d{8}$"; public final String value; /** diff --git a/src/main/java/seedu/address/model/person/Sex.java b/src/main/java/seedu/address/model/person/Sex.java new file mode 100644 index 00000000000..dec82d7c61b --- /dev/null +++ b/src/main/java/seedu/address/model/person/Sex.java @@ -0,0 +1,61 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's sex in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidSex(String)} + */ +public class Sex { + + public static final String MESSAGE_CONSTRAINTS = + "Sex should only be either 'm' or 'f'."; + public static final String VALIDATION_REGEX = "^[mf]$"; + public final String value; + + + /** + * Constructs an {@code Sex}. + * + * @param sex A valid sex. + */ + public Sex(String sex) { + requireNonNull(sex); + checkArgument(isValidSex(sex), MESSAGE_CONSTRAINTS); + value = sex; + } + + /** + * Returns true if a given string is a valid sex. + */ + public static boolean isValidSex(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Sex)) { + return false; + } + + Sex otherSex = (Sex) other; + return value.equals(otherSex.value); + } + + @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 index cc0a68d79f9..4cad0fbe65e 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -79,6 +79,44 @@ public void remove(Person toRemove) { } } + /** + * Archives the equivalent person from the list. + * The person must exist in the list. + */ + public void archive(Person target, Person archivedPerson) { + requireAllNonNull(target); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + if (!target.isSamePerson(archivedPerson) && contains(archivedPerson)) { + throw new DuplicatePersonException(); + } + + internalList.set(index, archivedPerson); + } + + /** + * Unarchives the equivalent person from the list. + * The person must exist in the list. + */ + public void unarchive(Person target, Person unarchivedPerson) { + requireAllNonNull(target); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + if (!target.isSamePerson(unarchivedPerson) && contains(unarchivedPerson)) { + throw new DuplicatePersonException(); + } + + internalList.set(index, unarchivedPerson); + } + public void setPersons(UniquePersonList replacement) { requireNonNull(replacement); internalList.setAll(replacement.internalList); @@ -104,6 +142,32 @@ public ObservableList asUnmodifiableObservableList() { return internalUnmodifiableList; } + /** + * Returns an ObservableList of unarchived persons. + */ + public ObservableList getUnarchivedPersonList() { + ObservableList unarchivedPersons = FXCollections.observableArrayList(); + for (Person person : internalList) { + if (!person.getArchiveStatus().getArchiveStatus()) { + unarchivedPersons.add(person); + } + } + return FXCollections.unmodifiableObservableList(unarchivedPersons); + } + + /** + * Returns an ObservableList of archived persons. + */ + public ObservableList getArchivedPersonList() { + ObservableList archivedPersons = FXCollections.observableArrayList(); + for (Person person : internalList) { + if (person.getArchiveStatus().getArchiveStatus()) { + archivedPersons.add(person); + } + } + return FXCollections.unmodifiableObservableList(archivedPersons); + } + @Override public Iterator iterator() { return internalList.iterator(); diff --git a/src/main/java/seedu/address/model/person/WorkHours.java b/src/main/java/seedu/address/model/person/WorkHours.java new file mode 100644 index 00000000000..3f22ae38334 --- /dev/null +++ b/src/main/java/seedu/address/model/person/WorkHours.java @@ -0,0 +1,54 @@ +package seedu.address.model.person; + +import java.util.Objects; + +/** + * Represents the work hours of a person. + */ +public class WorkHours { + public static final String MESSAGE_CONSTRAINTS = "Hours clocked can only contain numbers."; + public final int hoursWorked; + + /** + * Constructs a {@code WorkHours} object with zero hours worked. + */ + public WorkHours() { + this.hoursWorked = 0; + } + + public WorkHours(int hoursWorked) { + this.hoursWorked = hoursWorked; + } + + + /** + * Returns the number of hours worked. + * + * @return The number of hours worked. + */ + public int getHoursWorked() { + return hoursWorked; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + WorkHours workHours = (WorkHours) o; + return hoursWorked == workHours.hoursWorked; + } + + @Override + public int hashCode() { + return Objects.hash(hoursWorked); + } + + @Override + public String toString() { + return hoursWorked + " hours clocked"; + } +} diff --git a/src/main/java/seedu/address/model/schedule/Schedule.java b/src/main/java/seedu/address/model/schedule/Schedule.java new file mode 100644 index 00000000000..01d5c0bc94c --- /dev/null +++ b/src/main/java/seedu/address/model/schedule/Schedule.java @@ -0,0 +1,43 @@ +package seedu.address.model.schedule; + +import java.time.LocalDate; +import java.util.Map; +import java.util.Set; + +import seedu.address.model.person.Person; + +/** + * Represents a Schedule that maps lists of Persons to dates. + */ +public interface Schedule { + /** + * Returns the schedule as a Map. + */ + Set getScheduleDates(); + + /** + * Adds a Person to the schedule on the given date. + */ + void addPerson(Person person, LocalDate date); + + /** + * Deletes a Person from the schedule on the given date. + */ + void deletePerson(Person person, LocalDate date); + + /** + * Adds a ScheduleDate to the schedule. + */ + void addScheduleDate(ScheduleDate scheduleDate); + + /** + * Resets the existing data of this {@code Schedule} with {@code newData}. + */ + void resetData(Schedule newData); + + + /** + * Returns the number of hours worked by each person in the schedule between the given dates. + */ + Map getHoursWorked(LocalDate startDate, LocalDate endDate); +} diff --git a/src/main/java/seedu/address/model/schedule/ScheduleDate.java b/src/main/java/seedu/address/model/schedule/ScheduleDate.java new file mode 100644 index 00000000000..45e4fdb1cbf --- /dev/null +++ b/src/main/java/seedu/address/model/schedule/ScheduleDate.java @@ -0,0 +1,71 @@ +package seedu.address.model.schedule; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import seedu.address.model.person.Person; + +/** + * Represents a date in the schedule. + */ +public class ScheduleDate { + private final LocalDate date; + private final List persons; + + /** + * Constructs a {@code ScheduleDate} with the given {@code LocalDate}. + */ + public ScheduleDate(LocalDate date) { + this.date = date; + this.persons = new ArrayList<>(); + } + + /** + * Constructs a {@code ScheduleDate} with the given {@code LocalDate} and {@code List}. + */ + public LocalDate getDate() { + return date; + } + + /** + * Returns the list of people working on this date. + */ + public List getPersons() { + return persons; + } + + /** + * Adds someone working on this date. + */ + public void addPerson(Person person) { + persons.add(person); + } + + /** + * Removes someone working on this date. + */ + public void removePerson(Person person) { + persons.remove(person); + } + + /** + * Returns true if the person is working on this date. + */ + public boolean hasPerson(Person person) { + return persons.contains(person); + } + + @Override + public int hashCode() { + return date.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ScheduleDate // instanceof handles nulls + && date.equals(((ScheduleDate) other).date)) // state check + && persons.equals(((ScheduleDate) other).persons); + } +} diff --git a/src/main/java/seedu/address/model/schedule/ScheduleManager.java b/src/main/java/seedu/address/model/schedule/ScheduleManager.java new file mode 100644 index 00000000000..6b51c8b357c --- /dev/null +++ b/src/main/java/seedu/address/model/schedule/ScheduleManager.java @@ -0,0 +1,111 @@ +package seedu.address.model.schedule; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import seedu.address.model.person.Person; + +/** + * Represents a Schedule that maps lists of Persons to dates. + */ +public class ScheduleManager implements Schedule { + // The schedule is a set of ScheduleDates. + private final Set schedule; + + /** + * Creates a MonthSchedule with an empty schedule. + */ + public ScheduleManager() { + this.schedule = new HashSet<>(); + } + + @Override + public Set getScheduleDates() { + return schedule; + } + + @Override + public void addPerson(Person person, LocalDate date) { + for (ScheduleDate scheduleDate : schedule) { + if (scheduleDate.getDate().equals(date)) { + if (!scheduleDate.hasPerson(person)) { + scheduleDate.addPerson(person); + } + return; + } + } + ScheduleDate newScheduleDate = new ScheduleDate(date); + newScheduleDate.addPerson(person); + schedule.add(newScheduleDate); + } + + /** + * Adds a ScheduleDate to the schedule. + * Used only by JsonAdaptedSchedule to convert JSON data to a Schedule, + * so there is no need to check for duplicates. + * @param scheduleDate the ScheduleDate to add + */ + @Override + public void addScheduleDate(ScheduleDate scheduleDate) { + schedule.add(scheduleDate); + } + + @Override + public void deletePerson(Person person, LocalDate date) { + for (ScheduleDate scheduleDate : schedule) { + if (scheduleDate.getDate().equals(date)) { + if (scheduleDate.hasPerson(person)) { + scheduleDate.removePerson(person); + } + return; + } + } + } + + @Override + public Map getHoursWorked(LocalDate startDate, LocalDate endDate) { + Map hoursWorked = new HashMap<>(); + for (ScheduleDate scheduleDate : schedule) { + if (isAfterOrEqual(scheduleDate.getDate(), startDate) && isBeforeOrEqual(scheduleDate.getDate(), endDate)) { + for (Person person : scheduleDate.getPersons()) { + if (!hoursWorked.containsKey(person)) { + hoursWorked.put(person, 0.0); + } + hoursWorked.put(person, hoursWorked.get(person) + 8.0); + } + } + } + return hoursWorked; + } + + @Override + public void resetData(Schedule newData) { + schedule.clear(); + if (newData != null) { + schedule.addAll(newData.getScheduleDates()); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ScheduleManager // instanceof handles nulls + && schedule.equals(((ScheduleManager) other).schedule)); + } + private boolean isAfterOrEqual(LocalDate date1, LocalDate date2) { + if (date1 == null || date2 == null) { + return false; + } + return date1.isAfter(date2) || date1.isEqual(date2); + } + + private boolean isBeforeOrEqual(LocalDate date1, LocalDate date2) { + if (date1 == null || date2 == null) { + return false; + } + return date1.isBefore(date2) || date1.isEqual(date2); + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..91c3ce80afb 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,5 +1,6 @@ package seedu.address.model.util; +import java.time.LocalDate; import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; @@ -7,10 +8,16 @@ 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.ArchiveStatus; +import seedu.address.model.person.BankDetails; import seedu.address.model.person.Name; +import seedu.address.model.person.PayRate; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Sex; +import seedu.address.model.person.WorkHours; +import seedu.address.model.schedule.Schedule; +import seedu.address.model.schedule.ScheduleManager; import seedu.address.model.tag.Tag; /** @@ -19,24 +26,72 @@ 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 Person( + new Name("Alex"), + new Name("Yeoh"), + new Phone("87438807"), + new Sex("m"), + new PayRate(14.5), 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 BankDetails("dbs 1234567890"), + new WorkHours(), + getTagSet("friends"), + new ArchiveStatus(false)), + new Person( + new Name("Bernice"), + new Name("Yu"), + new Phone("99272758"), + new Sex("f"), + new PayRate(16), 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 BankDetails("ocbc 1234567"), + new WorkHours(), + getTagSet("colleagues", "friends"), + new ArchiveStatus(false)), + new Person( + new Name("Charlotte"), + new Name("Oliveiro"), + new Phone("93210283"), + new Sex("f"), + new PayRate(20), 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 BankDetails("hsbc 0987654321"), + new WorkHours(), + getTagSet("neighbours"), + new ArchiveStatus(false)), + new Person( + new Name("David"), + new Name("Li"), + new Phone("91031282"), + new Sex("m"), + new PayRate(18.5), 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 BankDetails("uob 8888777700"), + new WorkHours(), + getTagSet("family"), + new ArchiveStatus(false)), + new Person( + new Name("Irfan"), + new Name("Ibrahim"), + new Phone("92492021"), + new Sex("m"), + new PayRate(16.5), 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 BankDetails("posb 369369369"), + new WorkHours(), + getTagSet("classmates"), + new ArchiveStatus(false)), + new Person( + new Name("Roy"), + new Name("Balakrishnan"), + new Phone("92624417"), + new Sex("m"), + new PayRate(10), new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new BankDetails("ocbc 7654321"), + new WorkHours(), + getTagSet("colleagues"), + new ArchiveStatus(false)) }; } @@ -48,13 +103,24 @@ public static ReadOnlyAddressBook getSampleAddressBook() { return sampleAb; } + public static Schedule getSampleSchedule() { + Schedule sampleSchedule = new ScheduleManager(); + LocalDate date = LocalDate.now(); + int count = 0; + for (Person samplePerson : getSamplePersons()) { + if (count % 3 == 0) { + date = date.plusDays(1); + } + sampleSchedule.addPerson(samplePerson, date); + } + return sampleSchedule; + } + /** * 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()); + 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 index f2e015105ae..490685ad07b 100644 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ b/src/main/java/seedu/address/storage/AddressBookStorage.java @@ -41,5 +41,4 @@ public interface AddressBookStorage { * @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 index bd1ca0f56c8..b0034ab4cca 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -11,10 +11,14 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.person.Address; -import seedu.address.model.person.Email; +import seedu.address.model.person.ArchiveStatus; +import seedu.address.model.person.BankDetails; import seedu.address.model.person.Name; +import seedu.address.model.person.PayRate; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Sex; +import seedu.address.model.person.WorkHours; import seedu.address.model.tag.Tag; /** @@ -24,39 +28,61 @@ class JsonAdaptedPerson { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - private final String name; + private final String firstName; + private final String lastName; private final String phone; - private final String email; + private final String sex; + private final double payRate; private final String address; + private final String bankDetails; private final List tags = new ArrayList<>(); + private final int workHours; + private final boolean archiveStatus; /** * 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("tags") List tags) { - this.name = name; + public JsonAdaptedPerson(@JsonProperty("firstName") String firstName, + @JsonProperty("lastName") String lastName, + @JsonProperty("phone") String phone, + @JsonProperty("sex") String sex, + @JsonProperty("payRate") double payRate, + @JsonProperty("address") String address, + @JsonProperty("bankDetails") String bankDetails, + @JsonProperty("workHours") int workHours, + @JsonProperty("tags") List tags, + @JsonProperty("archiveStatus") boolean archiveStatus) { + this.firstName = firstName; + this.lastName = lastName; this.phone = phone; - this.email = email; + this.sex = sex; + this.payRate = payRate; this.address = address; + this.bankDetails = bankDetails; + this.workHours = workHours; if (tags != null) { this.tags.addAll(tags); } + this.archiveStatus = archiveStatus; } /** * Converts a given {@code Person} into this class for Jackson use. */ public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; + firstName = source.getFirstName().value; + lastName = source.getLastName().value; phone = source.getPhone().value; - email = source.getEmail().value; + sex = source.getSex().value; + payRate = source.getPayRate().getPayRate(); address = source.getAddress().value; + bankDetails = source.getBankDetails().value; + workHours = source.getWorkHours().getHoursWorked(); + archiveStatus = source.getArchiveStatus().getArchiveStatus(); tags.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); } /** @@ -70,13 +96,21 @@ public Person toModelType() throws IllegalValueException { personTags.add(tag.toModelType()); } - if (name == null) { + if (firstName == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); } - if (!Name.isValidName(name)) { + if (!Name.isValidName(firstName)) { throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); } - final Name modelName = new Name(name); + final Name modelFirstName = new Name(firstName); + + if (lastName == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(lastName)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelLastName = new Name(lastName); if (phone == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); @@ -86,13 +120,15 @@ public Person toModelType() throws IllegalValueException { } final Phone modelPhone = new Phone(phone); - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + if (sex == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Sex.class.getSimpleName())); } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); + if (!Sex.isValidSex(sex)) { + throw new IllegalValueException(Sex.MESSAGE_CONSTRAINTS); } - final Email modelEmail = new Email(email); + final Sex modelSex = new Sex(sex); + + final PayRate modelPayRate = new PayRate(payRate); if (address == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); @@ -102,8 +138,24 @@ public Person toModelType() throws IllegalValueException { } final Address modelAddress = new Address(address); + if (bankDetails == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, BankDetails.class.getSimpleName())); + } + if (!BankDetails.isValidBankAccount(bankDetails)) { + throw new IllegalValueException(BankDetails.MESSAGE_CONSTRAINTS); + } + final BankDetails modelBankDetails = new BankDetails(bankDetails); + + final WorkHours modelWorkHours = new WorkHours(workHours); + final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + + final ArchiveStatus modelArchiveStatus = new ArchiveStatus(archiveStatus); + + return new Person(modelFirstName, modelLastName, modelPhone, modelSex, modelPayRate, + modelAddress, + modelBankDetails, modelWorkHours, modelTags, modelArchiveStatus); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedScheduleDate.java b/src/main/java/seedu/address/storage/JsonAdaptedScheduleDate.java new file mode 100644 index 00000000000..97ef3047aeb --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedScheduleDate.java @@ -0,0 +1,62 @@ +package seedu.address.storage; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Person; +import seedu.address.model.schedule.ScheduleDate; + +/** + * Jackson-friendly version of {@link ScheduleDate}. + */ +class JsonAdaptedScheduleDate { + private final String date; + private final List persons = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedScheduleDate} with the given person details. + */ + @JsonCreator + public JsonAdaptedScheduleDate(@JsonProperty("date") String date, + @JsonProperty("persons") List persons) { + this.date = date; + if (persons != null) { + this.persons.addAll(persons); + } + } + + /** + * Converts a given {@code ScheduleDate} into this class for Jackson use. + */ + public JsonAdaptedScheduleDate(ScheduleDate source) { + date = source.getDate().toString(); + persons.addAll(source.getPersons().stream() + .map(JsonAdaptedPerson::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted person object into the model's {@code ScheduleDate} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person. + */ + public ScheduleDate toModelType() throws IllegalValueException { + final List persons = new ArrayList<>(); + for (JsonAdaptedPerson person : this.persons) { + persons.add(person.toModelType()); + } + final LocalDate modelDate = LocalDate.parse(date); + ScheduleDate scheduleDate = new ScheduleDate(modelDate); + for (Person person : persons) { + scheduleDate.addPerson(person); + } + return scheduleDate; + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java index 41e06f264e1..2a33c759770 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java @@ -31,6 +31,10 @@ public Path getAddressBookFilePath() { return filePath; } + public Path getArchiveBookFilePath() { + return filePath; + } + @Override public Optional readAddressBook() throws DataLoadingException { return readAddressBook(filePath); diff --git a/src/main/java/seedu/address/storage/JsonScheduleStorage.java b/src/main/java/seedu/address/storage/JsonScheduleStorage.java new file mode 100644 index 00000000000..c3c8373b676 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonScheduleStorage.java @@ -0,0 +1,80 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.DataLoadingException; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.FileUtil; +import seedu.address.commons.util.JsonUtil; +import seedu.address.model.schedule.Schedule; + +/** + * A class to access AddressBook data stored as a json file on the hard disk. + */ +public class JsonScheduleStorage implements ScheduleStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonScheduleStorage.class); + + private Path filePath; + + public JsonScheduleStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getScheduleFilePath() { + return filePath; + } + + @Override + public Optional readSchedule() throws DataLoadingException { + return readSchedule(filePath); + } + + /** + * Similar to {@link #readSchedule()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataLoadingException if loading the data from storage failed. + */ + public Optional readSchedule(Path filePath) throws DataLoadingException { + requireNonNull(filePath); + + Optional jsonSchedule = JsonUtil.readJsonFile( + filePath, JsonSerializableSchedule.class); + if (!jsonSchedule.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonSchedule.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataLoadingException(ive); + } + } + + @Override + public void saveSchedule(Schedule schedule) throws IOException { + saveSchedule(schedule, filePath); + } + + /** + * Similar to {@link #saveSchedule(Schedule)}. + * + * @param filePath location of the data. Cannot be null. + */ + public void saveSchedule(Schedule schedule, Path filePath) throws IOException { + requireNonNull(schedule); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableSchedule(schedule), filePath); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableSchedule.java b/src/main/java/seedu/address/storage/JsonSerializableSchedule.java new file mode 100644 index 00000000000..a1d792997ec --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonSerializableSchedule.java @@ -0,0 +1,56 @@ +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.schedule.Schedule; +import seedu.address.model.schedule.ScheduleDate; +import seedu.address.model.schedule.ScheduleManager; + +/** + * An Immutable Schedule that is serializable to JSON format. + */ +@JsonRootName(value = "schedule") +class JsonSerializableSchedule { + + private final List scheduleDates = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableAddressBook} with the given persons. + */ + @JsonCreator + public JsonSerializableSchedule(@JsonProperty("scheduleDates") List scheduleDates) { + this.scheduleDates.addAll(scheduleDates); + } + + /** + * 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 JsonSerializableSchedule(Schedule source) { + scheduleDates.addAll(source.getScheduleDates().stream() + .map(JsonAdaptedScheduleDate::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 Schedule toModelType() throws IllegalValueException { + Schedule schedule = new ScheduleManager(); + for (JsonAdaptedScheduleDate jsonAdaptedScheduleDate : scheduleDates) { + ScheduleDate scheduleDate = jsonAdaptedScheduleDate.toModelType(); + schedule.addScheduleDate(scheduleDate); + } + return schedule; + } +} diff --git a/src/main/java/seedu/address/storage/ScheduleStorage.java b/src/main/java/seedu/address/storage/ScheduleStorage.java new file mode 100644 index 00000000000..5044b75bf41 --- /dev/null +++ b/src/main/java/seedu/address/storage/ScheduleStorage.java @@ -0,0 +1,45 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataLoadingException; +import seedu.address.model.schedule.Schedule; + +/** + * Represents a storage for {@link seedu.address.model.schedule.Schedule}. + */ +public interface ScheduleStorage { + + /** + * Returns the file path of the data file. + */ + Path getScheduleFilePath(); + + /** + * Returns Schedule data as a {@link Schedule}. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataLoadingException if loading the data from storage failed. + */ + Optional readSchedule() throws DataLoadingException; + + /** + * @see #getScheduleFilePath() + */ + Optional readSchedule(Path filePath) throws DataLoadingException; + + /** + * Saves the given {@link Schedule} to the storage. + * @param schedule cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveSchedule(Schedule schedule) throws IOException; + + /** + * @see #saveSchedule(Schedule) + */ + void saveSchedule(Schedule schedule, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index 9fba0c7a1d6..0fdcd896ca5 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -8,11 +8,12 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; +import seedu.address.model.schedule.Schedule; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends AddressBookStorage, UserPrefsStorage, ScheduleStorage { @Override Optional readUserPrefs() throws DataLoadingException; @@ -29,4 +30,12 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { @Override void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + @Override + Path getScheduleFilePath(); + + @Override + Optional readSchedule() throws DataLoadingException; + + @Override + void saveSchedule(Schedule schedule) throws IOException; } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 8b84a9024d5..9e39b0604a0 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -10,6 +10,7 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; +import seedu.address.model.schedule.Schedule; /** * Manages storage of AddressBook data in local storage. @@ -19,13 +20,17 @@ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); private AddressBookStorage addressBookStorage; private UserPrefsStorage userPrefsStorage; + private ScheduleStorage scheduleStorage; /** - * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. + * Creates a {@code StorageManager} with the given {@code AddressBookStorage}, {@code UserPrefStorage}, + * and {@code ScheduleStorage}. */ - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage, + ScheduleStorage scheduleStorage) { this.addressBookStorage = addressBookStorage; this.userPrefsStorage = userPrefsStorage; + this.scheduleStorage = scheduleStorage; } // ================ UserPrefs methods ============================== @@ -45,7 +50,6 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { userPrefsStorage.saveUserPrefs(userPrefs); } - // ================ AddressBook methods ============================== @Override @@ -75,4 +79,32 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro addressBookStorage.saveAddressBook(addressBook, filePath); } + // ================ Schedule methods ============================== + @Override + public Path getScheduleFilePath() { + return scheduleStorage.getScheduleFilePath(); + } + + @Override + public Optional readSchedule() throws DataLoadingException { + return readSchedule(scheduleStorage.getScheduleFilePath()); + } + + @Override + public Optional readSchedule(Path filePath) throws DataLoadingException { + logger.fine("Attempting to read data from file: " + filePath); + return scheduleStorage.readSchedule(filePath); + } + + @Override + public void saveSchedule(Schedule schedule) throws IOException { + saveSchedule(schedule, scheduleStorage.getScheduleFilePath()); + } + + @Override + public void saveSchedule(Schedule schedule, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + scheduleStorage.saveSchedule(schedule, filePath); + } + } diff --git a/src/main/java/seedu/address/ui/Calendar.java b/src/main/java/seedu/address/ui/Calendar.java new file mode 100644 index 00000000000..455d98b26b8 --- /dev/null +++ b/src/main/java/seedu/address/ui/Calendar.java @@ -0,0 +1,156 @@ +/* + * Adapted from Da9el00's Calendar.fxml + * https://gist.github.com/Da9el00/f4340927b8ba6941eb7562a3306e93b6 + */ + +package seedu.address.ui; + +import java.time.LocalDate; +import java.time.Month; +import java.time.format.TextStyle; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javafx.fxml.FXML; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.scene.text.Text; +import seedu.address.model.person.Person; +import seedu.address.model.schedule.ScheduleDate; + +/** + * A graphic calendar that displays the schedule of a given month. + */ +public class Calendar extends UiPart { + private static final String FXML = "Calendar.fxml"; + + // Number of weeks to display in the calendar + private static int numberOfWeeks = 4; + + private final LocalDate today; + private final Set scheduleDates; + + @FXML + private FlowPane calendar; + + /** + * Creates a calendar with the given schedule dates. + * @param scheduleDates Schedule dates to display in the calendar. + */ + public Calendar(Set scheduleDates) { + super(FXML); + today = LocalDate.now(); + this.scheduleDates = scheduleDates; + drawCalendar(); + } + + /** + * Creates a calendar with the given schedule dates and today's date. + */ + private void drawCalendar() { + double calendarWidth = calendar.getPrefWidth(); + assert(calendarWidth >= 0); + double calendarHeight = calendar.getPrefHeight(); + assert(calendarHeight >= 0); + double strokeWidth = 1; + double spacingH = calendar.getHgap(); + assert(spacingH >= 0); + double spacingV = calendar.getVgap(); + assert(spacingV >= 0); + + // List of activities for a given month + Map scheduleDateMap = createCalendarMap(); + + Month month = today.getMonth(); + int day = today.getDayOfMonth() - today.getDayOfWeek().getValue(); + if (day <= 0) { + month = today.getMonth().minus(1); + day = month.maxLength() + day; + } + int currentMonthMaxDay = month.maxLength(); + + for (int i = 0; i < numberOfWeeks; i++) { + for (int j = 0; j < 7; j++) { + StackPane stackPane = new StackPane(); + Rectangle rectangle = new Rectangle(); + rectangle.setFill(Color.TRANSPARENT); + rectangle.setStroke(Color.BLACK); + rectangle.setStrokeWidth(strokeWidth); + double rectangleWidth = (calendarWidth / 7) - strokeWidth - spacingH; + rectangle.setWidth(rectangleWidth); + double rectangleHeight = (calendarHeight / numberOfWeeks) - strokeWidth - spacingV; + rectangle.setHeight(rectangleHeight); + stackPane.getChildren().add(rectangle); + String dayMonth = day + " " + month.getDisplayName(TextStyle.FULL, Locale.ENGLISH); + ScheduleDate scheduleDate = scheduleDateMap.get(LocalDate.of(today.getYear(), month, day)); + Text date = new Text(dayMonth); + double textTranslationY = -(rectangleHeight / 2) * 0.75; + date.setTranslateY(textTranslationY); + stackPane.getChildren().add(date); + if (scheduleDate != null) { + createCalendarActivity(scheduleDate, rectangleHeight, rectangleWidth, stackPane); + } + if (today.getDayOfMonth() == day && today.getMonth() == month) { + rectangle.setStroke(Color.BLUE); + } + calendar.getChildren().add(stackPane); + day++; + if (day > currentMonthMaxDay) { + day = 1; + month = month.plus(1); + currentMonthMaxDay = month.maxLength(); + } + } + } + } + + /** + * Creates a calendar activity for a given date. + * @param scheduleDate Schedule date to create the calendar activity for. + * @param rectangleHeight Height of the rectangle. + * @param rectangleWidth Width of the rectangle. + * @param stackPane Stack pane to add the calendar activity to. + */ + private void createCalendarActivity(ScheduleDate scheduleDate, double rectangleHeight, double rectangleWidth, + StackPane stackPane) { + VBox calendarActivityBox = new VBox(); + List persons = scheduleDate.getPersons(); + List personNames = persons.stream() + .map(Person::getName) + .map(x -> x.value) + .collect(Collectors.toList()); + for (int k = 0; k < persons.size(); k++) { + Text text = new Text(personNames.get(k)); + calendarActivityBox.getChildren().add(text); + } + calendarActivityBox.setTranslateY((rectangleHeight / 2) * 0.20); + calendarActivityBox.setMaxWidth(rectangleWidth * 0.8); + calendarActivityBox.setMaxHeight(rectangleHeight * 0.65); + stackPane.getChildren().add(calendarActivityBox); + } + + /** + * Creates a map of schedule dates with the date as the key. + * @return Map of schedule dates with the date as the key. + */ + private Map createCalendarMap() { + return scheduleDates.stream().collect( + Collectors.toMap(ScheduleDate::getDate, x -> x)); + } + + /** + * Sets the number of weeks to display in the calendar. + * @param numberOfWeeks Number of weeks to display in the calendar. + */ + public static void setNumberOfWeeks(int numberOfWeeks) { + assert(numberOfWeeks > 0); + Calendar.numberOfWeeks = numberOfWeeks; + } +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..48b7afe84d7 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -37,7 +37,7 @@ public CommandBox(CommandExecutor commandExecutor) { @FXML private void handleCommandEntered() { String commandText = commandTextField.getText(); - if (commandText.equals("")) { + if ("".equals(commandText)) { return; } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..5ac8b37310d 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2324s2-cs2103t-t17-4.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 79e74ef37c0..03425d42f78 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -9,6 +9,7 @@ import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; import javafx.stage.Stage; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; @@ -41,6 +42,9 @@ public class MainWindow extends UiPart { @FXML private MenuItem helpMenuItem; + @FXML + private MenuItem showContactsMenuItem; + @FXML private StackPane personListPanelPlaceholder; @@ -50,6 +54,12 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane showContactsButton; + + @FXML + private StackPane showScheduleButton; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -110,9 +120,8 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + personListPanel = new PersonListPanel(logic.getFilteredUnarchivedPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); - resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -121,6 +130,10 @@ void fillInnerParts() { CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + Text contactsButtonText = new Text("CONTACTS"); + showContactsButton.getChildren().add(contactsButtonText); + Text scheduleButtonText = new Text("SCHEDULE"); + showScheduleButton.getChildren().add(scheduleButtonText); } /** @@ -163,6 +176,24 @@ private void handleExit() { primaryStage.hide(); } + @FXML + private void handleShowContacts() { + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + } + + @FXML + private void handleShowPayroll() { + PayrollListPanel payrollListPanel = new PayrollListPanel(logic.getPayrollList()); + personListPanelPlaceholder.getChildren().add(payrollListPanel.getRoot()); + } + + @FXML + private void handleShowCalendar() { + Calendar calendar = new Calendar(logic.getScheduleDates()); + personListPanelPlaceholder.getChildren().add(calendar.getRoot()); + } + public PersonListPanel getPersonListPanel() { return personListPanel; } @@ -186,6 +217,18 @@ private CommandResult executeCommand(String commandText) throws CommandException handleExit(); } + if (commandResult.isShowContacts()) { + handleShowContacts(); + } + + if (commandResult.isShowSchedule()) { + handleShowCalendar(); + } + + if (commandResult.isShowPayroll()) { + handleShowPayroll(); + } + return commandResult; } catch (CommandException | ParseException e) { logger.info("An error occurred while executing command: " + commandText); diff --git a/src/main/java/seedu/address/ui/PayrollCard.java b/src/main/java/seedu/address/ui/PayrollCard.java new file mode 100644 index 00000000000..3d227ff49cf --- /dev/null +++ b/src/main/java/seedu/address/ui/PayrollCard.java @@ -0,0 +1,57 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import seedu.address.model.person.PayrollWrapper; + + +/** + * An UI component that displays information of a {@code Person}. + */ +public class PayrollCard extends UiPart { + + private static final String FXML = "PayrollListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final PayrollWrapper payroll; + + @FXML + private HBox cardPane; + @FXML + private VBox vBox; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private FlowPane tags; + @FXML + private Label phone; + + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public PayrollCard(PayrollWrapper payroll, int displayedIndex) { + super(FXML); + this.payroll = payroll; + id.setText(displayedIndex + ". "); + name.setText(payroll.getPerson().getName().value); + phone.setText(payroll.getPerson().getPhone().value); + Label bankDetails = new Label(payroll.getPerson().getBankDetails().value); + Label hoursWorked = new Label(payroll.getHoursWorked() + " hours"); + Label pay = new Label("$" + payroll.getPay()); + vBox.getChildren().addAll(bankDetails, hoursWorked, pay); + } +} diff --git a/src/main/java/seedu/address/ui/PayrollListPanel.java b/src/main/java/seedu/address/ui/PayrollListPanel.java new file mode 100644 index 00000000000..3b8048a979f --- /dev/null +++ b/src/main/java/seedu/address/ui/PayrollListPanel.java @@ -0,0 +1,40 @@ +package seedu.address.ui; + +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.model.person.PayrollWrapper; + +/** + * Panel containing the list of payrolls. + */ +public class PayrollListPanel extends UiPart { + private static final String FXML = "PayrollListPanel.fxml"; + + @FXML + private ListView payrollListView; + + /** + * Creates a {@code PayrollListPanel} with the given {@code ObservableList}. + */ + public PayrollListPanel(ObservableList payrollList) { + super(FXML); + payrollListView.setItems(payrollList); + payrollListView.setCellFactory(listView -> new PayrollListViewCell()); + } + + class PayrollListViewCell extends ListCell { + @Override + protected void updateItem(PayrollWrapper payroll, boolean empty) { + super.updateItem(payroll, empty); + if (empty || payroll == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PayrollCard(payroll, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 094c42cda82..27428e8ec43 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -7,8 +7,10 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import seedu.address.model.person.Person; + /** * An UI component that displays information of a {@code Person}. */ @@ -29,31 +31,48 @@ public class PersonCard extends UiPart { @FXML private HBox cardPane; @FXML + private VBox vBox; + @FXML private Label name; @FXML private Label id; @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML private FlowPane tags; + @FXML + private Label phone; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. */ - public PersonCard(Person person, int displayedIndex) { + public PersonCard(Person person, int displayedIndex, boolean isFullView) { super(FXML); this.person = person; id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); + name.setText(person.getName().value); phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + + if (isFullView) { + Label sex = new Label(getFullSexString(person.getSex().value)); + Label payRate = new Label(person.getPayRate().toString()); + Label address = new Label(person.getAddress().value); + Label bankDetails = new Label(person.getBankDetails().value); + // Label hoursWorked = new Label(person.getWorkHours().toString()); + vBox.getChildren().addAll(new Label[]{sex, payRate, address, bankDetails}); + } + } + + /** + * Returns the full sex string based on the short form string returned from Sex. + */ + private String getFullSexString(String shortFormString) { + if (shortFormString.equals("m")) { + return "Male"; + } else if (shortFormString.equals("f")) { + return "Female"; + } + return "Invalid Sex"; // should not reach here } } diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index f4c501a897b..308c76d5e4b 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -1,13 +1,10 @@ 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; /** @@ -15,7 +12,8 @@ */ public class PersonListPanel extends UiPart { private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); + private int selectedIndex = -1; + private boolean isFullView = false; @FXML private ListView personListView; @@ -27,6 +25,16 @@ public PersonListPanel(ObservableList personList) { super(FXML); personListView.setItems(personList); personListView.setCellFactory(listView -> new PersonListViewCell()); + personListView.setOnMousePressed(event -> { + // If the same person is selected, toggle between full view and normal view + if (selectedIndex == personListView.getSelectionModel().getSelectedIndex()) { + isFullView = !isFullView; + } else { + isFullView = true; + } + selectedIndex = personListView.getSelectionModel().getSelectedIndex(); + personListView.refresh(); + }); } /** @@ -41,7 +49,8 @@ protected void updateItem(Person person, boolean empty) { setGraphic(null); setText(null); } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); + setGraphic(new PersonCard(person, getIndex() + 1, + getIndex() == selectedIndex && isFullView).getRoot()); } } } diff --git a/src/main/resources/view/Calendar.fxml b/src/main/resources/view/Calendar.fxml new file mode 100644 index 00000000000..82cc61ccbcb --- /dev/null +++ b/src/main/resources/view/Calendar.fxml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 124283a392e..aaeb1ff9174 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -3,7 +3,6 @@ - - + + - diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 7778f666a0a..2f09aa15ce0 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -1,60 +1,68 @@ - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PayrollListCard.fxml b/src/main/resources/view/PayrollListCard.fxml new file mode 100644 index 00000000000..8988e70d8f6 --- /dev/null +++ b/src/main/resources/view/PayrollListCard.fxml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PayrollListPanel.fxml b/src/main/resources/view/PayrollListPanel.fxml new file mode 100644 index 00000000000..b63c5e0923f --- /dev/null +++ b/src/main/resources/view/PayrollListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f5e812e25e6..961ade74dd0 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -7,30 +7,32 @@ + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml index a1bb6bbace8..fd4589a3cc0 100644 --- a/src/main/resources/view/PersonListPanel.fxml +++ b/src/main/resources/view/PersonListPanel.fxml @@ -3,6 +3,6 @@ - + diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index 01b691792a9..3b16e31a628 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -3,7 +3,6 @@ - -