diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000000..b735373365bc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000000..066b2d920a28 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index 823d175eb670..e686ad968d87 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ lib/* *.log.* *.csv config.json +!_reposense/config.json src/test/data/sandbox/ preferences.json .DS_Store diff --git a/README.adoc b/README.adoc index 450054624f48..ab1264e7358f 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,14 @@ -= Address Book (Level 4) += MeNUS ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +_Improves the **m**anagement and its **e**fficiency of your restaurants in **NUS**._ + +https://travis-ci.org/CS2103-AY1819S1-F10-4/main[image:https://travis-ci.org/CS2103-AY1819S1-F10-4/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/AZhiKai/main-j2jk6[image:https://ci.appveyor.com/api/projects/status/5kwkxt5khmfo0q31/branch/master?svg=true[Build status]] +https://coveralls.io/github/CS2103-AY1819S1-F10-4/main?branch=master[image:https://coveralls.io/repos/github/CS2103-AY1819S1-F10-4/main/badge.svg?branch=master[Coverage Status]] +https://www.codacy.com/app/AZhiKai/organisation_main?utm_source=github.com&utm_medium=referral&utm_content=CS2103-AY1819S1-F10-4/main&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/299f6df960044215a511a90d281af954[Codacy code quality]] +https://github.com/CS2103-AY1819S1-F10-4/main/blob/master/LICENSE[image:https://img.shields.io/badge/license-MIT-orange.svg[MIT]] +https://github.com/CS2103-AY1819S1-F10-4/main[image:https://img.shields.io/badge/team-F10%204-blue.svg[Team F10-4]] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,26 +18,30 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* This is a desktop menu application. It has a GUI but most of the user interactions happen using a CLI (Command Line +Interface) which is oriented for typist user. +* The target user group is F&B owners who operate one or more restaurants in NUS. This application aims to improve +the effectiveness and efficiency of managing their restaurants. Essentially, *MeNUS* is the one and only restaurant +management system they will ever need. == Site Map * <> * <> -* <> * <> * <> == Acknowledgements +* This application was built based on https://github.com/se-edu/addressbook-level4[Address Book (Level 4)], an +initiative of the https://se-edu.github.io/Team.html[se-edu] team. * Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. -* Libraries used: https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5] +* Libraries used: https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], + https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], + https://github.com/junit-team/junit5[JUnit5], https://github.com/patrickfav/bcrypt[bcrypt] +* Free icons: https://www.flaticon.com/authors/smashicons[Smashicons], https://thenounproject.com/term/my-account/219377/[User account icon], +http://free-icon-rainbow.com/restaurant-menu-free-icon-3/[Menu icon], http://www.iconarchive.com/show/ios7-icons-by-icons8/Food-Bunch-Ingredients-icon.html[Ingredient icon], +https://icons8.com/icon/pack/city/dotty[Reservation icon], https://mbtskoudsalg.com/explore/sales-icons-png/[Sales icon], http://img.grouponcdn.com/deal/6116wnYU3ci3GdedPMKw/hE-1000x600[Chicken Burger Set photo] == Licence : link:LICENSE[MIT] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..9ed0b1fc7512 --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,30 @@ +{ + "authors": + [ + { + "githubId": "AZhiKai", + "displayName": "ANG ZHI KAI", + "authorNames": ["AZhiKai", "Ang Zhi Kai"] + }, + { + "githubId": "yican95", + "displayName": "LEE YI CAN", + "authorNames": ["yican95"] + }, + { + "githubId": "HyperionNKJ", + "displayName": "NEO KAI JUN", + "authorNames": ["HyperionNKJ"] + }, + { + "githubId": "m4dkip", + "displayName": "ONG MING XIAN", + "authorNames": ["m4dkip"] + }, + { + "githubId": "rebstan97", + "displayName": "TAN YING ZI REBECCA", + "authorNames": ["rebstan97", "Rebecca"] + } + ] +} diff --git a/build.gradle b/build.gradle index f8e614f8b49b..40fc72ce248c 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ if (JavaVersion.current() == JavaVersion.VERSION_1_10 } // Specifies the entry point of the application -mainClassName = 'seedu.address.MainApp' +mainClassName = 'seedu.restaurant.MainApp' sourceCompatibility = JavaVersion.VERSION_1_9 targetCompatibility = JavaVersion.VERSION_1_9 @@ -58,6 +58,8 @@ dependencies { String testFxVersion = '4.0.12-alpha' String jUnitVersion = '5.1.0' + compile group: 'com.joestelmach', name: 'natty', version: '0.12' + implementation group: 'org.controlsfx', name: 'controlsfx', version: '8.40.11' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' @@ -66,6 +68,7 @@ dependencies { implementation group: 'com.sun.xml.bind', name: 'jaxb-impl', version: '2.3.0' implementation group: 'com.sun.xml.bind', name: 'jaxb-core', version: '2.3.0' implementation group: 'javax.activation', name: 'activation', version: '1.1.1' + implementation group: 'at.favre.lib', name: 'bcrypt', version: '0.5.0' testImplementation group: 'junit', name: 'junit', version: '4.12' testImplementation group: 'org.testfx', name: 'testfx-core', version: testFxVersion, { @@ -77,12 +80,12 @@ dependencies { testRuntimeOnly group: 'org.testfx', name: 'testfx-internal-java9', version: testFxVersion testRuntimeOnly group: 'org.testfx', name: 'openjfx-monocle', version: 'jdk-9+181' - testRuntimeOnly group:'org.junit.vintage', name:'junit-vintage-engine', version: jUnitVersion + testRuntimeOnly group: 'org.junit.vintage', name: 'junit-vintage-engine', version: jUnitVersion testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'menus.jar' destinationDir = file("${buildDir}/jar/") } @@ -93,7 +96,7 @@ task wrapper(type: Wrapper) { task coverage(type: JacocoReport) { sourceDirectories = files(sourceSets.main.allSource.srcDirs) - classDirectories = files(sourceSets.main.output) + classDirectories = files(sourceSets.main.output) executionData = files(jacocoTestReport.executionData) afterEvaluate { classDirectories = files(classDirectories.files.collect { @@ -157,16 +160,16 @@ test { } if (runNonGuiTests) { - test.include 'seedu/address/**' + test.include 'seedu/restaurant/**' } if (runGuiTests) { test.include 'systemtests/**' - test.include 'seedu/address/ui/**' + test.include 'seedu/restaurant/ui/**' } if (!runGuiTests) { - test.exclude 'seedu/address/ui/**' + test.exclude 'seedu/restaurant/ui/**' } } } @@ -176,10 +179,10 @@ task headless { println 'Setting headless mode properties.' test { systemProperties = [ - 'testfx.robot': 'glass', - 'testfx.headless': 'true', - 'prism.order': 'sw', - 'prism.text': 't2k', + 'testfx.robot' : 'glass', + 'testfx.headless': 'true', + 'prism.order' : 'sw', + 'prism.text' : 't2k', ] } } @@ -194,22 +197,21 @@ asciidoctor { outputDir "${buildDir}/docs" options = [ - template_dirs: [file("${sourceDir}/templates")], + template_dirs: [file("${sourceDir}/templates")], ] attributes = [ - linkcss: true, - stylesheet: 'gh-pages.css', - 'source-highlighter': 'coderay', - icons: 'font', - experimental: true, - sectlinks: true, - idprefix: '', // for compatibility with GitHub preview - idseparator: '-', - 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level4', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level4', - 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) + linkcss : true, + stylesheet : 'gh-pages.css', + 'source-highlighter': 'coderay', + icons : 'font', + experimental : true, + sectlinks : true, + idprefix : '', // for compatibility with GitHub preview + idseparator : '-', + 'site-root' : "${sourceDir}", // must be the same as sourceDir, do not modify + 'site-name' : 'MeNUS', + 'site-githuburl' : 'https://github.com/CS2103-AY1819S1-F10-4/main', ] options['template_dirs'].each { @@ -229,7 +231,7 @@ asciidoctor.dependsOn copyStylesheets task deployOfflineDocs(type: Copy) { into('src/main/resources/docs') - from ("${asciidoctor.outputDir}/html5") { + from("${asciidoctor.outputDir}/html5") { include 'stylesheets/*' include 'images/*' include 'HelpWindow.html' diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index e647ed1e715a..0c4f5e519d72 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,49 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + -{empty} + +MeNUS was developed by the https://CS2103-AY1819S1-F10-4.github.io/main/AboutUs.html[Project Team] +shown below based on the previous work of the https://se-edu.github.io/Team.html[se-edu] team. + + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== Ang Zhi Kai +{empty}[http://www.comp.nus.edu.sg/~azhikai[homepage]] [https://github.com/azhikai[github]] [<>] -Role: Project Advisor +Role: Team Lead, Schedule + Tracking + +Responsibilities: Model + Storage ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Tan Ying Zi Rebecca +{empty}[http://github.com/rebstan97[github]] [<>] -Role: Team Lead + +Role: Testing + Quality Assurance + Responsibilities: UI ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Ong Ming Xian +{empty}[http://github.com/m4dkip[github]] [<>] -Role: Developer + -Responsibilities: Data +Role: Documentation + +Responsibilities: Logic ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Lee Yi Can +{empty}[http://github.com/yican95[github]] [<>] -Role: Developer + -Responsibilities: Dev Ops + Threading +Role: Integration + +Responsibilities: Logic ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Neo Kai Jun +{empty}[http://github.com/hyperionnkj[github]] [<>] -Role: Developer + -Responsibilities: UI +Role: Integration, Code quality, Schedule + Tracking + +Responsibilities: Storage + UI ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 5de5363abffd..730773ef8dff 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,6 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. -* *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Bug reports, Suggestions* : Post in our https://github.com/CS2103-AY1819S1-F10-4/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here]. +* *Email us* : You can also reach us at `azhikai [at] comp.nus.edu.sg` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 817ec81d7832..99a322e7b637 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += MeNUS - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -13,25 +13,58 @@ ifdef::env-github[] :warning-caption: :warning: :experimental: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103-AY1819S1-F10-4/main/tree/master -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Team CS2103-AY1819S1-F10-4`      Since: `Sep 2018`      Licence: `MIT` + +// tag::intro[] +== Welcome to MeNUS +This Developer Guide is written by the *MeNUS* v1.4 team for the benefits of future developers and maintainers of the +application. + +* This guide includes instructions for setting up the development environment. + +* This guide provides sufficient UMLs (unified model diagrams) to illustrate the architectural structure and design +methodology. + +* This guide offers advice for troubleshooting some common issues. + +*MeNUS* is an open-source project. If you are interested in contributing, see the +https://cs2103-ay1819s1-f10-4-menus.netlify.com/contactus[`Contact Us`] page for more information. + +=== Legend +The following *3* callouts will be used throughout the documentation which you may wish to pay attention to as it may +contain important details: + +[NOTE] +Just for your info, do not be alarmed. Be sure to read these notes as it might contain some important information. + +[TIP] +Perhaps something can be done using another approach, but it is up to you to decide. Tips are often not important and + can be safely ignored. + +[WARNING] +Some things might go wrong if you are not careful, or did not follow the instructions correctly. You are strongly +advised to read whatever is in this block. +// end::intro[] == Setting up +Follow this setup guide to get *MeNUS* up and running on your computer. === Prerequisites -. *JDK `9`* or later +. *JDK `9`* + [WARNING] JDK `10` on Windows will fail to run tests in <> due to a https://github.com/javafxports/openjdk-jfx/issues/66[JavaFX bug]. -Windows developers are highly recommended to use JDK `9`. +Windows developers are highly recommended to use JDK `9` which can be downloaded +https://www.oracle.com/technetwork/java/javase/downloads/java-archive-javase9-3934878.html[here] . *IntelliJ* IDE + [NOTE] -IntelliJ by default has Gradle and JavaFx plugins installed. + -Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plugins` to re-enable them. +IntelliJ by default has Gradle and JavaFX plugins installed. + +Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plugins` to re-enable them === Setting up the project in your computer @@ -47,21 +80,23 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu . Click `OK` to accept the default settings . Open a console and run the command `gradlew processResources` (Mac/Linux: `./gradlew processResources`). It should finish with the `BUILD SUCCESSFUL` message. + This will generate all resources required by the application and tests. -. Open link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson.java`] and link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow.java`] and check for any code errors +. Open link:{repoURL}/src/main/java/seedu/restaurant/storage/XmlAdaptedAccount.java[`XmlAdaptedAccount.java`] and +link:{repoURL}/src/main/java/seedu/restaurant/ui/MainWindow.java[`MainWindow.java`] and check for any code errors .. Due to an ongoing https://youtrack.jetbrains.com/issue/IDEA-189060[issue] with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully .. To resolve this, place your cursor over any of the code section highlighted in red. Press kbd:[ALT + ENTER], and select `Add '--add-modules=...' to module compiler options` for each error -. Repeat this for the test folder as well (e.g. check link:{repoURL}/src/test/java/seedu/address/commons/util/XmlUtilTest.java[`XmlUtilTest.java`] and link:{repoURL}/src/test/java/seedu/address/ui/HelpWindowTest.java[`HelpWindowTest.java`] for code errors, and if so, resolve it the same way) +. Repeat this for the test folder as well (e.g. check link:{repoURL}/src/test/java/seedu/restaurant/commons/util/XmlUtilTest.java[`XmlUtilTest.java`] and link:{repoURL}/src/test/java/seedu/restaurant/ui/HelpWindowTest.java[`HelpWindowTest.java`] for code errors, and if so, resolve it the same way) === Verifying the setup -. Run the `seedu.address.MainApp` and try a few commands +. Run the `seedu.restaurant.MainApp` and try a few commands. . <> to ensure they all pass. === Configurations to do before writing code ==== Configuring the coding style -This project follows https://github.com/oss-generic/process/blob/master/docs/CodingStandards.adoc[oss-generic coding standards]. IntelliJ's default style is mostly compliant with ours but it uses a different import order from ours. To rectify, +This project follows https://github.com/oss-generic/process/blob/master/docs/CodingStandards.adoc[oss-generic coding +standards]. IntelliJ's default style is mostly compliant with ours but it uses a different import order from ours. To rectify: . Go to `File` > `Settings...` (Windows/Linux), or `IntelliJ IDEA` > `Preferences...` (macOS) . Select `Editor` > `Code Style` > `Java` @@ -72,16 +107,6 @@ This project follows https://github.com/oss-generic/process/blob/master/docs/Cod Optionally, you can follow the <> document to configure Intellij to check style-compliance as you write code. -==== Updating documentation to match your fork - -After forking the repo, the documentation will still have the SE-EDU branding and refer to the `se-edu/addressbook-level4` repo. - -If you plan to develop this fork as a separate product (i.e. instead of contributing to `se-edu/addressbook-level4`), you should do the following: - -. Configure the <> in link:{repoURL}/build.gradle[`build.gradle`], such as the `site-name`, to suit your own project. - -. Replace the URL in the attribute `repoURL` in link:{repoURL}/docs/DeveloperGuide.adoc[`DeveloperGuide.adoc`] and link:{repoURL}/docs/UserGuide.adoc[`UserGuide.adoc`] with the URL of your fork. - ==== Setting up CI Set up Travis to perform Continuous Integration (CI) for your fork. See <> to learn how to set it up. @@ -89,7 +114,7 @@ Set up Travis to perform Continuous Integration (CI) for your fork. See <>). [NOTE] -Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your personal fork. +Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your personal fork Optionally, you can set up AppVeyor as a second CI (see <>). @@ -101,22 +126,25 @@ Having both Travis and AppVeyor ensures your App works on both Unix-based platfo When you are ready to start coding, 1. Get some sense of the overall design by reading <>. -2. Take a look at <>. + +=== Using Git with Sourcetree +We use Git with Sourcetree for distributed source control. See <> if you find any difficulty +when using Git with Sourcetree. == Design [[Design-Architecture]] === Architecture -.Architecture Diagram image::Architecture.png[width="600"] +_Figure 3.1.1: Architecture Diagram_ The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick overview of each component. [TIP] The `.pptx` files used to create diagrams in this document can be found in the link:{repoURL}/docs/diagrams/[diagrams] folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose `Save as picture`. -`Main` has only one class called link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp`]. It is responsible for, +`Main` has only one class called link:{repoURL}/src/main/java/seedu/restaurant/MainApp.java[`MainApp`]. It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup method where necessary. @@ -140,24 +168,26 @@ Each of the four components For example, the `Logic` component (see the class diagram given below) defines it's API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class. -.Class Diagram of the Logic Component + image::LogicClassDiagram.png[width="800"] +_Figure 3.1.2: Class Diagram of the Logic Component_ [discrete] ==== Events-Driven nature of the design The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `delete 1`. -.Component interactions for `delete 1` command (part 1) -image::SDforDeletePerson.png[width="800"] +image::SDforDeleteItem.png[width="800"] +_Figure 3.1.3: Component interactions for `delete 1` command (part 1)_ [NOTE] -Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. +Note how the `Model` simply raises a `RestaurantBookChangedEvent` when the Restaurant Book data are changed, instead of +asking the `Storage` to save the updates to the hard disk. The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. -.Component interactions for `delete 1` command (part 2) -image::SDforDeletePersonEventHandling.png[width="800"] +image::SDforDeleteItemEventHandling.png[width="800"] +_Figure 3.1.4: Component interactions for `delete 1` command (part 2)_ [NOTE] Note how the event is propagated through the `EventsCenter` to the `Storage` and `UI` without `Model` having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components. @@ -167,14 +197,15 @@ The sections below give more details of each component. [[Design-Ui]] === UI component -.Structure of the UI Component image::UiClassDiagram.png[width="800"] +_Figure 3.2.1: Structure of the UI Component_ -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +*API* : link:{repoURL}/src/main/java/seedu/restaurant/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `AccountListPanel`, +`StatusBarFooter`, `DetailPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. -The `UI` component uses 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 link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] +The `UI` component uses 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 link:{repoURL}/src/main/java/seedu/restaurant/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] The `UI` component, @@ -186,59 +217,56 @@ The `UI` component, === Logic component [[fig-LogicClassDiagram]] -.Structure of the Logic Component image::LogicClassDiagram.png[width="800"] +_Figure 3.3.1: Structure of the Logic Component_ *API* : -link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +link:{repoURL}/src/main/java/seedu/restaurant/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `RestaurantBookParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. +. The command execution can affect the `Model` (e.g. adding an account) and/or raise events. . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. -Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. +Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("register +id/azhikai pw/1122qq")` API call. -.Interactions Inside the Logic Component for the `delete 1` Command -image::DeletePersonSdForLogic.png[width="800"] +image::RegisterAccountSdForLogic.png[width="800"] +_Figure 3.3.2: Interactions Inside the Logic Component for the `delete 1` Command_ [[Design-Model]] === Model component -.Structure of the Model Component -image::ModelClassDiagram.png[width="800"] +image::ModelClassBetterOopDiagram.png[width="800"] +_Figure 3.4.1: Structure of the Model Component_ -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +*API* : link:{repoURL}/src/main/java/seedu/restaurant/model/Model.java[`Model.java`] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the Restaurant Book data. +* exposes data via 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. * does not depend on any of the other three components. -[NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + - + -image:ModelClassBetterOopDiagram.png[width="800"] - [[Design-Storage]] === Storage component -.Structure of the Storage Component image::StorageClassDiagram.png[width="800"] +_Figure 3.5.1: Structure of the Storage Component_ -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +*API* : link:{repoURL}/src/main/java/seedu/restaurant/storage/Storage.java[`Storage.java`] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in xml format and read it back. +* can save the Restaurant Book data in xml format and read it back. [[Design-Commons]] === Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.restaurant.commons` package. == Implementation @@ -248,54 +276,72 @@ This section describes some noteworthy details on how certain features are imple === Undo/Redo feature ==== Current Implementation -The undo/redo mechanism is facilitated by `VersionedAddressBook`. -It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. +The undo/redo mechanism is facilitated by `VersionedRestaurantBook`. +It extends `RestaurantBook` with an undo/redo history, stored internally as an `restaurantBookStateList` 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. +* `VersionedRestaurantBook#commit()` -- Saves the current restaurant book state in its history. +* `VersionedRestaurantBook#undo()` -- Restores the previous restaurant book state from its history. +* `VersionedRestaurantBook#redo()` -- Restores a previously undone restaurant 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#RestaurantBook()`, `Model#undoRestaurantBook()` and +`Model#redoRestaurantBook()` 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 `VersionedRestaurantBook` will be initialized with the initial restaurant book state, and the `currentStatePointer` pointing to that single restaurant book state. image::UndoRedoStartingStateListDiagram.png[width="800"] -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 `deregister id/azhikai` command to delete the account with the username of `azhikai` from the +restaurant book. The `deregister` command calls `Model#commitRestaurantBook()`, causing the modified state of the +restaurant book after the `deregister id/azhikai` command executes to be saved in the `restaurantBookStateList`, and +the `currentStatePointer` is shifted to the newly inserted restaurant book state. image::UndoRedoNewCommand1StateListDiagram.png[width="800"] -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 `register id/azhikai ...` to add a new account. The `register` command also calls +`Model#commitRestaurantBook()`, causing another modified restaurant book state to be saved into the `restaurantBookStateList`. image::UndoRedoNewCommand2StateListDiagram.png[width="800"] [NOTE] -If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +If a command fails its execution, it will not call `Model#commitRestaurantBook()`, so the restaurant book state will not be saved into the `restaurantBookStateList`. -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 account was a mistake, and decides to undo that action by executing the +`undo` command. The `undo` command will call `Model#undoRestaurantBook()`, which will shift the `currentStatePointer` +once to the left, pointing it to the previous restaurant book state, and restores the restaurant book to that state. image::UndoRedoExecuteUndoStateListDiagram.png[width="800"] [NOTE] -If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. +If the `currentStatePointer` is at index 0, pointing to the initial restaurant book state, then there are no previous +restaurant book states to restore. The `undo` command uses `Model#canUndoRestaurantBook()` to check if this is the +case. If so, it will return an error to the user rather than attempting to perform the undo. The following sequence diagram shows how the undo operation works: image::UndoRedoSequenceDiagram.png[width="800"] +_Figure 4.1.1.1: Structure of the Storage Component_ -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#redoRestaurantBook()`, which shifts the `currentStatePointer` +once to the right, pointing to the previously undone state, and restores the restaurant book to that state. [NOTE] -If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book 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. +If the `currentStatePointer` is at index `restaurantBookStateList.size() - 1`, pointing to the latest restaurant book + state, then there are no undone restaurant book states to restore. The `redo` command uses + `Model#canRedoRestaurantBook()` 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 restaurant book, such as + `list`, will usually not call `Model#commitRestaurantBook()`, `Model#undoRestaurantBook()` or + `Model#redoRestaurantBook()`. Thus, the `restaurantBookStateList` remains unchanged. image::UndoRedoNewCommand3StateListDiagram.png[width="800"] -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. We designed it this way because 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#commitRestaurantBook()`. Since the `currentStatePointer` is not +pointing at the end of the `restaurantBookStateList`, all restaurant book states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. image::UndoRedoNewCommand4StateListDiagram.png[width="800"] @@ -307,29 +353,619 @@ image::UndoRedoActivityDiagram.png[width="650"] ===== Aspect: How undo & redo executes -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire restaurant book. ** 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). +** Pros: Will use less memory (e.g. for `deregister`, just save the account being deleted). ** Cons: We must ensure that the implementation of each individual command are correct. ===== Aspect: Data structure to support the undo/redo commands -* **Alternative 1 (current choice):** Use a list to store the history of address book states. +* **Alternative 1 (current choice):** Use a list to store the history of restaurant book states. ** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`. +** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both +`HistoryManager` and `VersionedRestaurantBook`. * **Alternative 2:** Use `HistoryManager` for undo/redo ** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. ** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. // end::undoredo[] -// tag::dataencryption[] -=== [Proposed] Data Encryption +// tag::useraccount[] +=== User account feature +==== Current Implementation + +The user account mechanism is facilitated by `RestaurantBook`. Additionally, it implements the following operations: + +* `RestaurantBook#getAccount(Account)` -- Retrieves the account. +* `RestaurantBook#addAccount(Account)` -- Saves the new account. +* `RestaurantBook#updateAccount(Account, Account)` -- Update the existing account. +* `RestaurantBook#removeAccount(Account)` -- Removes the account. + +These operations are exposed in the `Model` interface as `Model#getAccount(Account)`, `Model#addAccount(Account)`, +`Model#updateAccount(Account, Account)` and `Model#removeAccount(Account)`. The following commands will +invoke the aforementioned operations: + +* `Command#LoginCommand()` -- Invokes `Model#getAccount(Account)`. +* `Command#RegisterCommand()` -- Invokes `Model#addAccount(Account)`. +* `Command#DeregisterCommand()` -- Invokes `Model#removeAccount(Account)`. +* `Command#ChangePasswordCommand()` -- Invokes `Model#updateAccount(Account, Account)`. + +Given below are example usage scenarios and how each of the command and its respective operations behave at each +step which involves two components, `Logic` which is responsible for parsing the user input and `Model` which is +responsible for manipulating the list, if necessary. Both components are extended by `LogicManager` and +`ModelManager` respectively. + +The following sequence diagram shows how the `register` command works: + +image::RegisterSequenceDiagram.png[width="800"] +_Figure 4.2.1.1: Sequence diagram to illustrate component interactions for the `register` command_ + +[NOTE] +==== +** We assume the user is already logged in to an account with appropriate privilege level (e.g. Administrator) +** If the username already exists, a warning message will be shown to the user to select another username +** The `deregister` command works the same way by checking if the account exists before calling `Model#removeAccount(Account)` +==== + +Step 1. The user executes `register id/azhikai pw/1122qq n/Ang Zhi Kai` command to create a new user account. + +Step 2. `LogicManager` invokes the `RestaurantBookParser#parseCommand()` method which takes in the user input +as arguments. + +Step 3. When the command is parsed, the `Command#RegisterCommand()` will be created which is returned to the +`LogicManager`. + +Step 4. `LogicManager` invokes the `execute()` method of the `Command#RegisterCommand()`, `rc` which is instantiated in +Step 3. The `Model` component will be involved as the `Command#RegisterCommand()` invokes a request to add the account +into the storage by calling `Model#addAccount(Account)`. + +Step 5: The new account is added into the storage. Then, a `CommandResult` is generated and returned to +`LogicManager` which is used to display the result to the user. + +The following sequence diagram shows how the `login` command works: + +image::LoginSequenceDiagram.png[width="800"] +_Figure 4.2.1.2: Sequence diagram to illustrate component interactions for the `login` command_ + +[NOTE] +We assume the user will enter the correct password. Otherwise, warning message will be shown to the user to re-enter +the credential + +Step 1. The user executes `login id/azhikai pw/1122qq` command to login to an existing user account. + +Step 2. `LogicManager` invokes the `RestaurantBookParser#parseCommand()` method which takes in the user input +as arguments. + +Step 3. When the command is parsed, the `Command#LoginCommand()` will be created which is returned to the +`LogicManager`. + +Step 4. `LogicManager` invokes the `execute()` method of the `Command#LoginCommand()`, `lc` which is instantiated in +Step 3. The `Model` component will be involved as the `Command#LoginCommand()` invokes a request to retrieve an account +based on the username. If it exists, the account will be retrieved and the password hash will be compared. If it +matches, then the credential is valid and the user is authenticated. + +The following activity diagrams summarize how password is processed during the login and registration process: + +image::PasswordLoginActivityDiagram.png[width="650"] +_Figure 4.2.1.3: Activity diagram to illustrate how password is processed for the `login` command_ + +image::PasswordHashActivityDiagram.png[width="650"] +_Figure 4.2.1.4: Activity diagram to illustrate how password is processed for the `register` command_ + +==== Design Considerations +===== Aspect: How password is secured + +* **Alternative 1 (current choice):** Use username to generate cryptographic salt. +** Rationale: If we allow the `bcrypt` library to generate the salt, testing ability will be limited as the salt is +generated based on system time by default. This means that despite testing the same account with the same raw password, + its +equality will never match. Hence, a workaround is to generate the salt based on its username which is unique to each +account. +** Pros: Easy to implement and improves testing ability. +** Cons: If an attacker knows how the salt is generated, it could pose a security risk. +* **Alternative 2:** Use salt generated by the `bcrypt` library with high cost factor. +** Pros: Higher cost factor makes hashing of the password harder. Ideally, the cost factor should as high as the +system can tolerate, given the other tasks the system must otherwise fulfill. +** Cons: More processing resources used and existing test cases must be updated. + +=== User session feature +==== Current Implementation + +[NOTE] +==== +If a privileged command is executed when a session is not set, an error will be shown. For more information on which +commands are guest-executable, see the <> for more +information +==== + +The application's user session state is facilitated by `UserSession`. This is triggered by raising a `Login` or +`Logout` event upon executing the `Command#LoginCommand()` or `Command#LogoutCommand()` respectively. + + +Additionally, it implements the following static operations: + +* `UserSession#create(Account)` -- Set a user session. +* `UserSession#destroy()` -- Removes the existing user session. +* `UserSession#update(Account)` -- Updates the existing user session. +* `UserSession#isAuthenticated()` -- Checks if there is an existing login session. + +The following activity diagram summarizes how the user session is modified when a user logs in or out: + +image::UserSessionActivityDiagram.png[width="650"] +_Figure 4.3.1.1: Activity diagram to illustrate the user session_ + +The following code snippet demonstrates how these static methods are implemented: + +[source,java] +---- +/** + * Stores this {@link Account} info as part of this session. + * + * @param account logged in for this session. + */ +public static void create(Account acc) { + if (!isAuthenticated) { + isAuthenticated = true; + account = acc; + } +} + +/** + * Logs out of this account which releases this session. + */ +public static void destroy() { + isAuthenticated = false; + account = null; +} + +/** + * Updates the session with the new account info, such as updating of account password. + * + * @param acc that has been updated. + */ +public static void update(Account acc) { + if (isAuthenticated) { + account = acc; + } +} +---- + +==== Design Considerations +===== Aspect: How user session is handled + +* **Alternative 1 (current choice):** Use static flags and methods. +** Pros: Easy to implement, given the constraints. +** Cons: Can only support one user at any time. If another user wants to login, the current logged in user must log out. +In addition, since it is a global class object, test cases may be affected when testing both guest and privileged +commands which requires user to be logged out and logged in respectively. +* **Alternative 2:** Store a list of user sessions to allow multiple login. +** Pros: More user can login and manage the systems concurrently. Potentially able to resolve the global dependency +of the current solution which might affect the test ability. +** Cons: More memory usage to track each user session as the application scales with more users. +// end::useraccount[] + +// tag::salesmanagement[] + +// tag::auto-ingredient-update[] + +[[auto-ingredient-update]] +=== Auto-ingredient update feature + +The auto-ingredient update mechanism is facilitated by `RecordSalesCommand` and triggers whenever the "record-sales" +command is invoked. A `SalesRecord` will be instantiated based on the information given and attempts to compute +the ingredients used before deducting them from the ingredient list automatically. + +==== Current Implementation + +A `SalesRecord` is associated with 6 attributes - `Date`, `ItemName`, `QuantitySold`, `Price`, `Revenue` and +`IngredientUsed`. + +**The success of the auto-ingredient update mechanism is subjected to the following conditions:** + + +[[criteria]] +1) `ItemName` exists in the `Menu`. + +2) The required ingredients to make one unit of `ItemName` is specified in the `Menu`. + +3) The required ingredients exist in the `Ingredient` list. + +4) There are sufficient ingredients to make `QuantitySold` units of `ItemName` in the `Ingredient` list. + +[NOTE] +If any of the above conditions are not satisfied, sales volume will be recorded without updating the ingredient list + +[NOTE] +If conditions 1 and 2 are satisfied, `RecordSalesCommand` will compute all the ingredients used and store the data in + `IngredientUsed` attribute of `SalesRecord` + +This mechanism is aided by methods from the `Menu` and `Ingredient` components, all of which are exposed in the +`Model` interface. Given below is an example scenario and how the auto-ingredient update mechanism behaves at each step. + +**Step 1**. The user executes `record-sales d/11-01-2018 n/Fried Rice q/35 p/5.50` command to record the sales volume of +`Fried Rice` on `11-01-2018`. A `SalesRecord` would be instantiated based on the command arguments given. + +**Step 2**. `RecordSalesCommand` will request for the item `Fried Rice` from `Menu`. This is done through the `Item +findItem (Name)` method given in `Menu` component. This also checks if <> is satisfied. + +[[required-ingredient]] +**Step 3**. `RecordSalesCommand` then proceeds to request for the required ingredients to make a unit of `Fried Rice` +from `Menu`. This is done through the `Map getRequiredIngredients(Item)` method given +in `Menu`. This also checks if <> is satisfied. + +**Step 4**. With the required ingredients per unit data now at hand, `RecordSalesCommand` will compute the total +ingredients used in making `35` units of `Fried Rice`. The `IngredientUsed` attribute in `SalesRecord` will then be updated. + +**Step 5**. `RecordSalesCommand` will then pass the computed `IngredientUsed` to `Ingredient` component to request for + an update of ingredients. This is done through the `void consumeIngredients(Map)` method + given in `Ingredient` component. This checks for <>. + +**Step 6**. The `SalesRecord` is then finally added into `UniqueRecordList` via the `void addRecord(SalesRecord)` +method given in `Sales` component of `Model`. + +[NOTE] +An exception will be thrown in step 2, 3 or 5 should the conditions be violated. In such scenario, +`RecordSalesCommand` will jump to step 6 instantly and omit the remaining steps + +The following activity diagram (Figure 4.4.1.1) summarizes what happens when a user executes `record-sales` command: + +image::RecordSalesActivityDiagram.png[width="1000"] +_Figure 4.4.1.1: Activity Diagram for `record-sales` command_ +// end::salesmanagement[] + +==== Design Considerations +**Aspect: Should the auto-ingredient update mechanism be incorporated when deleting sales record?** + +* **Alternative 1 (current choice):** `DeleteSalesCommand` does not update the ingredient list. That is, it does not +"return" the ingredients which may have been deducted during `record-sales`. +** Pros: Remove the possibility of unwanted updates to the ingredient list. This is apparent when deleting +an obsolete record. We do not want the ingredients to "magically appear" in the ingredient list. +** Cons: If user accidentally recorded sales by mistake and triggered the update mechanism, deleting sales will not +help him/her recover the deducted ingredients. Instead, user will have to rely on the `Undo` command. + +* **Alternative 2:** `DeleteSalesCommand` "returns" any deducted ingredients to the ingredient list. +** Pros: Resolves the issue stated in "Alternative 1 - Cons". +** Cons: Brings about the issue stated in "Alternative 1 - Pros". + +**Aspect: Should the auto-ingredient update mechanism be incorporated when editing sales record?** + +* **Alternative 1 (current choice):** `EditSalesCommand` does not update the ingredient list. That is, it does not +"return" the ingredients which may have been deducted during `record-sales` and does not attempt to re-compute or +re-deduct the ingredient used. Since the `IngredientUsed` attribute of the `SalesRecord` is now outdated, it will be +removed permanently. +** Pros: Easy to implement. Avoids the implementation complexity as stated in "Alternative 2 - Cons". +** Cons: If user accidentally made an error when recording sales in the past, editing sales will not help him/her alter + the ingredients deducted. What was deducted previously will stay as it is. Also, the `IngredientUsed` attribute will no +longer be available. + +* **Alternative 2:** `EditSalesCommand` "returns" the ingredients which may have been deducted, re-computes the +`IngredientUsed` attribute and re-deduct the ingredients from the ingredient list. The `IngredientUsed` attribute of +the `SalesRecord` will be updated. + +** Pros: Resolves the issue stated in "Alternative 1 - Cons". +** Cons: Must take time into consideration when re-computing the `IngredientUsed` since `required ingredients per +unit` of an item may change over time. + +*** **Option 1**: Use `required ingredients per unit` data available **during record-sales in the past**. + +**Analysis**: We would need a repository to save different versions of `Menu` in the past. +The entire `Menu` must be saved whenever a sales record is added. This is so that if user were to edit the `ItemName` +attribute of that particular sales record in the future, we would be able to retrieve the item's `required +ingredients per unit` data (which might not even have been specified) at the time of `record-sales`. + +**Verdict**: **This option would take up a massive amount of memory space in the long run.** + +*** **Option 2**: Use `required ingredients per unit` data available **during edit-sales at the present**. + +**Analysis**: Let us assume that a glass of orange juice takes 1 orange to make at the time of record-sales (past), +and 3 oranges to make at the time of edit-sales (present). If the user edits the `QuantitySold` attribute from 1 +(past) to 10 (present), it would be illogical for (3*10-1*1 = 29) oranges to be deducted from the ingredient list since + it only took (1*10 = 10) oranges. + +**Verdict**: **This option is irrational**. + +// tag::salesmanagement1[] +=== Display sales report feature +==== Current Implementation + +The display sales report mechanism is facilitated by the Model, UI and Event components of the App. A `SalesReport` +class encapsulates the attributes of a sales report to be displayed. +The sales report is internally generated by `generateSalesReport(Date)` in `UniqueRecordList`. It then +propagates up the `Model` call hierarchy to `getSalesReport(Date)` in `ModelManager`, which is exposed in the `Model` +interface. + +The following sequence diagram (Figure 4.5.1.1) illustrates how the display sales report operation works when the +user enters `display-sales 25-12-2017`: + +image::DisplaySalesSequenceDiagram.png[width="1300"] +_Figure 4.5.1.1: Sequence Diagram for `display-sales` command_ + +The sequence diagram for the "Parse Command" reference frame above is shown below in Figure 4.5.1.2: + +image::DisplaySalesSequenceDiagramRef.png[width="580"] +_Figure 4.5.1.2: Sequence Diagram for "Parse Command" reference frame_ + +Given below is an example usage scenario and how the display sales report operation behaves at each step. + +Step 1. The user executes `display-sales 25-12-2017` command to request for the sales report dated 25-12-2017. The +`display-sales` command calls `Model#getSalesReport(Date)`, passing in the date "25-12-2017", and gets the generated +`SalesReport` in return. + +[NOTE] +`display-sales` command will not call `Model#getSalesReport(Date)` if the specified date is invalid + +[NOTE] +If no sales record associated with the given `Date` is found, an empty SalesReport will be +returned. In such cases, the command will terminate with a message notifying the user about the absence of such record + +Step 2. The `display-sales` command then raises the `DisplaySalesReportEvent` event, which also encapsulates the +generated `SalesReport` in step 1. + +Step 3. The `EventsCenter` reacts to the above event, which results in `handleDisplaySalesReportEvent(Event)` +in UI's `MainWindow` being called. This method instantiates a `SalesReportWindow` object by passing in the +`SalesReport` to its constructor. This `SalesReportWindow` acts as the controller for the sales report window. + +Step 4. The `SalesReportWindow` is then initialized and displayed on user's screen. + +==== Design Considerations +===== Aspect: How `SalesReport` is generated internally +* **Alternative 1 (current choice):** `generateSalesReport(Date)` in `UniqueRecordList` filters the entire record + list. Records that match the given `Date` are added into a `List`. The `SalesReport` is + generated based on this list. +** Pros: Easy to implement. +** Cons: Execution is of linear time complexity and would be considerably slow should the list size be very large. +* **Alternative 2:** Maintain another list that sorts itself by date every time it is modified. Conduct a binary +search to fill in the `List` every time a sales report is requested. +** Pros: `SalesReport` can be generated with a O(logN) time complexity. +** Cons: Sorting after every input would require O(NlogN) time which is slow. Additionally, the sorted list also +takes up an O(N) memory space. +// end::salesmanagement1[] + +// tag::ingredient[] +=== Delete ingredient feature +This feature allows the user to delete an ingredient from the ingredient list, specified by its name or index. + +==== Current Implementation + +The delete ingredient mechanism is facilitated by `DeleteIngredientCommand`. It is implemented as an abstract class +with the following abstract methods: + +* `DeleteIngredientCommand#execute()` – Removes a specified ingredient from the list +* `DeleteIngredientCommand#equals()` – Checks if two `DeleteIngredientCommand` instances have the same attributes + +`DeleteIngredientByIndexCommand` and `DeleteIngredientByNameCommand` extends `DeleteIngredientCommand` and implement +their own behaviour for these methods. + +* `DeleteIngredientCommandByIndexCommand#execute()` - Removes an ingredient specified by a valid index +* `DeleteIngredientCommandByNameCommand#execute()` - Removes an ingredient specified by a valid name + +The following sequence diagrams illustrate how the delete operation works. + +* Diagram 1: When user enters `delete-ingredient 1` + +image::DeleteIngredientByIndexSequenceDiagram.png[width="800"] + +_Figure 4.6.1.1: Component interactions for `delete-ingredient 1` command_ + +* Diagram 2: When user enters `delete-ingredient apple` ++ +image::DeleteIngredientByNameSequenceDiagram.png[width="800"] +_Figure 4.6.1.2: Component interactions for `delete-ingredient apple` command_ + +==== Design Considerations + +===== Aspect: Implementation of delete ingredient command +Two different implementations were considered. + +* **Alternative 1 (current choice):** Separate classes to handle deleting by index and deleting by name. +** Pros: Allows different attributes and method implementation for each class. +** Cons: Tight coupling between `DeleteIngredientCommand` and the inheriting classes +* **Alternative 2:** Single class to handle deleting by both index and name. +** Pros: Less coupling since the methods related to the delete ingredient command are confined to a single class. +** Cons: Two attributes are required, but only one has a value while the other has to be set to `null`. This makes the +`equals()` method difficult to implement. + +=== Stock up ingredient feature +This feature allows the user to stock up one or more ingredients in the ingredient list. + +==== Current Implementation +The Stock Up mechanism involves mainly the `StockUpCommandParser`, `StockUpCommand` and the `Model` +interface. The `StockUpCommandParser` parses the command input from the user, creating a `HashMap` mapping +`IngredientName` keys to `NumUnits` values. This `HashMap` represents the ingredients to be stocked up, and their +respective stock up amounts. With this `HashMap`, the `StockUpCommandParser` creates a `StockUpCommand` object. The +`StockUpCommand` executes the stock up operation, which is exposed in the `Model` interface as +`Model#stockUpIngredients()`. + +[WARNING] +The operation is atomic, thus if it fails for one ingredient, none of the ingredients will be stocked up and an +exception is thrown + +The following activity diagram shows what happens when the user executes the `stockup` command: + +image::StockUpActivityDiagram.png[width="1000"] +_Figure 4.7.1.1: Flow of events after a user executes the `stockup` command_ + +==== Design Considerations + +===== Aspect: Implementation of Stock Up command +Two different implementations were considered. + +* **Alternative 1 (current choice):** Create a stock up operation exposed in the `Model`. A list of ingredients is +passed as a `HashMap` argument to the model interface to perform the stock up on all ingredients. +** Pros: Maintains good abstraction. The sales feature can call upon the stock up operation of the interface without +needing to know its implementation or creating any Ingredient objects. +** Cons: Additional code has to be written and maintained, as opposed to using existing methods. +* **Alternative 2:** Use the edit operation of the `Model` interface exposed as `Model#updateIngredient()`. For each +ingredient to be stocked up, a new ingredient with the updated number of units is created and then passed as an +argument to `updateIngredient()`. +** Pros: Simple to implement, by using the existing `updateIngredient()` method. +** Cons: Difficult to integrate different features while maintaining abstraction. The +<> feature would require the sales component to create each +updated ingredient, before calling the `updateIngredient()` method. This means poor abstraction and possibly a +violation of the Law of Demeter. + +// end::ingredient[] + +// tag::menu[] +=== Menu management feature +Menu management feature extends `MeNUS` with a menu and provides the users with the ability to add items to the +menu, edit items and remove items from the menu. + +==== Current Implementation +The menu is stored internally as `items`, which is a `UniqueItemList` object that contains a list of `Item` objects. +The menu management feature implements the following operations: + +* `add-item` command -- Adds an item to the menu. The item must not already exist in the menu. +* `edit-item` command -- Replaces the target item with the editedItem. Target item + must be in the menu and editedItem must not be the same as another existing item in the menu. +* `delete-item-index` or `delete-item-name` command -- Removes the equivalent item(s) from the menu. +The item(s) must exist in the menu. +* `list-items` command -- Lists all the items in the menu. +* `clear-menu` command -- Removes all items from the menu. +* `select-item` command -- Selects an item in the menu and loads the page of the selected item. +* `sort-menu` -- Sorts the menu by name or price. The user must specify the sorting method. +* `find-item` command -- Finds items whose names contain any of the given keywords. +* `filter-menu` command -- Finds items whose tags contain any of the given keywords. +* `today-special` command -- Finds items whose tags contain the `DAY_OF_THE_WEEK`. +* `discount-item` command -- Gives discount to the equivalent item(s) in the menu. The item(s) must exist in the menu. +* `recipe-item` command -- Adds a recipe to the equivalent item in the menu. The item must exist in the menu. +* `add-required-ingredients` command -- Adds the required ingredients to the equivalent item in the menu. The item +must exist in the menu. + +Each `Item` object consists of `Name`, `Price`, `Recipe`, `Set` and `Map` + +==== `sort-menu` Command +The `sort-menu` command is facilitated by `SortMenuCommand` and `SortMenuCommandParser`. The command takes in +user input for the sorting method. +[NOTE] +Currently only sort by name or price + +The `SortMenuCommandParser` will parse the user input and checks if the input is valid. If the input is valid, it +constructs an `SortMenuCommand` object. +[NOTE] +If the input is not valid, it will throw a `ParseException` + +The `SortMenuCommand` object will indirectly call the `UniqueItemList#sortItemsByName()` or +`UniqueItemList#sortItemsByPrice()` and the sorts the menu. +After sorting the menu, it will save the current state for undo/redo. + +The following activity diagram summarizes what happens when a user executes `sort-menu` command: + +image::SortMenuActivityDiagram.png[width="650"] +_Figure 4.8.2.1: SortMenu Activity diagram_ + +The following sequence diagram shows how the `sort-menu` command works: + +image::SortMenuSequenceDiagram.png[width="1000"] +_Figure 4.8.2.2: SortMenu Sequence diagram_ + +==== `discount-item` Command +The `discount-item` command is facilitated by `DiscountItemCommand` and `DiscountItemCommandParser`. The command +takes in user input for discount percentage and index or the keyword `all`. + +The `DiscountItemCommandParser` will parse the user input and checks if the input is valid. It will check if the +keyword `all` is entered. If keyword `all` is entered, `DiscountItemCommandParser` will construct an +`DiscountItemCommand` object with `isAll` set to true. If index or a range of index is entered, it will construct an +`DiscountItemCommand` object with `isAll` set to false. +[NOTE] +If the inputs are not valid, it will throw a `ParseException` + +The `DiscountItemCommand` object will check if `isAll` is true. If `isAll` is true, it will give the discount to all +items in the `UniqueItemList`. Otherwise, it will give the discount to the item(s) specified by the index or the +range of index. The `DiscountItemCommand` object create an `Item with discounted price` and indirectly call +`UniqueItemList#setItem(Item target, Item editedItem)` to update the `Item` with `Item with discounted price`. It will +then save the current state for undo/redo. + +The following activity diagram summarizes what happens when a user executes `discount-item` command: + +image::DiscountItemActivityDiagram.png[width="650"] +_Figure 4.8.3.1: DiscountItem Activity diagram_ + +==== Design Considerations + +===== Aspect: How `Price` is parsed + +* **Alternative 1 (current choice):** `Price` is parsed without the currency symbol. +** Pros: Easy to implement. +** Cons: Only able to display `Price` with `$` with 2 decimal place. +* **Alternative 2:** Allow users to enter the currency symbol +** Pros: Able to display the different currencies. +** Cons: Harder to parse as currencies have different decimal places. Additional checks need to be implemented +to check if the currency symbol and price entered are valid. +// end::menu[] + +//tag::reservations[] +=== Reservations feature +The Reservations feature allows users to store customer reservations, view them, and to cancel them. + +==== Current Implementation +The Reservations feature currently contains 6 commands to modify the `UniqueReservationsList` stored in `ModelManager`. + +* `add-reservation` command - Adds reservations to the reservations list. +* `edit-reservation` command - Edits existing reservations in the reservations list. +* `delete-reservation` command - Deletes existing reservations in the reservations list. +* `list-reservation` command - Lists reservations in the reservations list. +* `select-reservation` command - Select existing reservations in the reservations list. +* `sort-reservation` command - Sorts existing reservations in the reservations list. + +Each `Reservation` object contains `Name`, `Pax`, `Date` and `Time`. + +==== `add-reservation` Command +The command takes in 3 parameters, `Name`, `Pax`,`Date` and `Time` to create a `Reservation` object. +[NOTE] +==== +The `Date` class will reject dates that have already passed the current date + +Dates are parsed using the `Natty` Library +==== + +[WARNING] +==== +Due to the way `Natty` is implemented, `Natty` will try to parse dates in `MM-DD-YYYY` format + +i.e. `03-11-2019` will be parsed as `11th March` instead of `3rd November` + +As a temporary workaround, the `Date` class will check to see if the date entered is in the +`DD-MM-YYYY` format + +If it is, then it will be parsed using `LocalDate.parse()` + +If not, it will continue use the `Natty` Library to parse the date value +==== + +After the `Reservation` Object is created, `RestaurantBook#addReservation(Reservation reservation)` is called to add the +`Reservation` Object to the `UniqueReservationsList`. + +The following activity diagram summarizes what happens when a user executes `add-reservation` command: + +image::AddReservationActivityDiagram.png[width="650"] +_Figure 4.9.2.1: Activity diagram to illustrate the `add-reservation` command_ + +==== `edit-reservation` Command +The command takes in 1 mandatory parameter, `Index`, followed by **1 or more** of the following optional parameters, + `Name`, `Pax`, `Date`, `Time`. + +The `Reservation` associated with the given `Index` is then identified within the internal `UniqueReservationsList`, +then has its values updated to the new values specified by the `Name`, `Pax`,`Date`, and `Time` parameters. + +==== `delete-reservation` Command +The command takes in 1 parameter, `Index`. + +The `Reservation` associated with the given `Index` is then identified, then deleted from the internal +`UniqueReservationsList`. + +==== `list-reservations` Command +The command does not require any additional parameters. + +A `DisplayReservationListRequestEvent` is posted to the `EventsCenter`, which will call the +`handleDisplayReservationListRequestEvent` method of the `MainWindow`. The Reservations List will then be shown on +the UI. + +The following sequence diagram shows how the `list-reservations` command works: -_{Explain here how the data encryption feature will be implemented}_ +image::ListReservationsSequenceDiagram.png[width="1000"] +_Figure 4.9.5.1: Sequence diagram to illustrate the `list-reservations` command_ -// end::dataencryption[] +==== Design Considerations +*Aspect: How `Date` and `Time` are parsed* + +* **Alternative 1 (current choice):** `Date` and `Time` are parsed using Natty. +** Pros: Easy to implement as it only requires importing the Natty library with minimal configuration. +** Cons: Natty will sometimes try to "guess" unexpected `Date` values. + +* **Alternative 2:** Configure the Natty library to avoid unexpected parse results. +** Pros: The parser will be able to provide more accurate `Date` and `Time` values. +** Cons: Difficult to implement as it requires deep understanding of how the Natty library works. +//end::reservations[] === Logging @@ -378,8 +1014,8 @@ Here are the steps to convert the project documentation files to PDF format. . Within Chrome, click on the `Print` option in Chrome's menu. . Set the destination to `Save as PDF`, then click `Save` to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below. -.Saving documentation as PDF files in Chrome image::chrome_save_as_pdf.png[width="300"] +_Figure 5.3.1.: Saving documentation as PDF files in Chrome_ [[Docs-SiteWideDocSettings]] === Site-wide Documentation Settings @@ -489,14 +1125,14 @@ We have two types of tests: . *GUI Tests* - These are tests involving the GUI. They include, .. _System Tests_ that test the entire App by simulating user actions on the GUI. These are in the `systemtests` package. -.. _Unit tests_ that test the individual components. These are in `seedu.address.ui` package. +.. _Unit tests_ that test the individual components. These are in `seedu.restaurant.ui` package. . *Non-GUI Tests* - These are tests not involving the GUI. They include, .. _Unit tests_ targeting the lowest level methods/classes. + -e.g. `seedu.address.commons.StringUtilTest` +e.g. `seedu.restaurant.commons.StringUtilTest` .. _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working). + -e.g. `seedu.address.storage.StorageManagerTest` +e.g. `seedu.restaurant.storage.StorageManagerTest` .. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together. + -e.g. `seedu.address.logic.LogicManagerTest` +e.g. `seedu.restaurant.logic.LogicManagerTest` === Troubleshooting Testing @@ -505,6 +1141,11 @@ e.g. `seedu.address.logic.LogicManagerTest` * Reason: One of its dependencies, `HelpWindow.html` in `src/main/resources/docs` is missing. * Solution: Execute Gradle task `processResources`. +**Problem: Test failed with `Missing newline at end of file`.** + +* Reason: A newline is missing in the file. +* Solution: `File` > `Settings...` > `Editor` > `General` > `Ensure line feed at file end on Save`. + == Dev Ops === Build Automation @@ -526,345 +1167,599 @@ When a pull request has changes to asciidoc files, you can use https://www.netli Here are the steps to create a new release. -. Update the version number in link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp.java`]. +. Update the version number in link:{repoURL}/src/main/java/seedu/restaurant/MainApp.java[`MainApp.java`]. . Generate a JAR file <>. . Tag the repo with the version number. e.g. `v0.1` . https://help.github.com/articles/creating-releases/[Create a new release using GitHub] and upload the JAR file you created. === Managing Dependencies -A project often depends on third-party libraries. For example, Address Book depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + -a. Include those libraries in the repo (this bloats the repo size) + -b. Require developers to download those libraries manually (this creates extra work for developers) +A project often depends on third-party libraries. For example, Restaurant Book depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + +a. Include those libraries in the repo (this bloats the repo size). + +b. Require developers to download those libraries manually (this creates extra work for developers). -[[GetStartedProgramming]] [appendix] -== Suggested Programming Tasks to Get Started +== Product Scope -Suggested path for new programmers: +*Target user profile*: -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <>. +* is a owner of one or more restaurant in National University of Singapore. +* prefers having PC application to handle his/her restaurant. +* can type reasonably fast. +* prefers typing over mouse input. +* is reasonably comfortable using CLI apps. -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <> explains how to go about adding such a feature. +*Value proposition*: efficiently and effectively manage restaurant without the need to invest in a complicated and +expensive system. -[[GetStartedProgramming-EachComponent]] -=== Improving each component +[appendix] +== User Stories -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -[discrete] -==== `Logic` component +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |restaurant owner |have my system protected |ensure only authorised staffs can access the system -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. +|`* * *` |restaurant owner |modify staff account |update my staff's data -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. +|`* * *` |restaurant owner |delete staff account |remove system access for an ex-staff -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** +|`* * *` |restaurant owner |assign role to a staff account |ensure only authorised staff can access certain parts of +the system -[discrete] -==== `Model` component +|`* * *` |new restaurant owner|see usage instructions |refer to instructions when I forget how to use the application -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. +|`* * *` |forgetful restaurant owner |see usage instructions |refer to instructions when I forget how to use the +application -[TIP] -Do take a look at <> before attempting to modify the `Model` component. - -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -**** +|`* * *` |restaurant owner |check the current availability of ingredients|manage my ingredients easily -[discrete] -==== `Ui` component +|`* * *` |restaurant owner |see which ingredients are low in stock count |know which ingredients to restock promptly -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. +|`* * *` |restaurant owner |record sales volume of an item within a day |analyse an item's sales performance in the future -[TIP] -Do take a look at <> before attempting to modify the `UI` component. +|`* * *` |restaurant owner |keep track of daily sales |meet revenue goals, improve the menu and track inventory -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** -+ -image::getting-started-ui-tag-before.png[width="300"] -+ -**After** -+ -image::getting-started-ui-tag-after.png[width="300"] -+ -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** +|`* * *` |restaurant owner |modify past sales records |update any mistakes / refunds / cancelled booking -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). -+ -**Before** -+ -image::getting-started-ui-result-before.png[width="200"] -+ -**After** -+ -image::getting-started-ui-result-after.png[width="200"] -+ -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** +|`* * *` |analytical restaurant owner |see the sales chart of revenue against date |have an overview of my restaurant financial performance and oversee its growth in the long run -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. -+ -**Before** -+ -image::getting-started-ui-status-before.png[width="500"] -+ -**After** -+ -image::getting-started-ui-status-after.png[width="500"] -+ -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** +|`* * *` |profit-driven restaurant owner |know the items that are bringing in the greatest revenue |employ marketing strategies to maximise profit -[discrete] -==== `Storage` component +|`* * *` |profit-driven restaurant owner |know the days in which revenue are greatest |employ marketing strategies to maximise profit -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. +|`* * *` |restaurant owner |add a new item to the menu |introduce new dishes -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. +|`* * *` |restaurant owner |delete an item from the menu |remove entries that I no longer need -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. -+ -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** +|`* * *` |restaurant owner |edit an item from the menu |update the entries of the menu -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` +|`* * *` |restaurant owner |find an item by name |locate details of items without having to go through the entire + list -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. +|`* * *` |restaurant owner |filter items by tag |filter and find items without having to go through the entire list -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. +|`* * *` |restaurant owner |give an item discount |have discount for items in menu -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` +|`* * *` |restaurant owner |view menu |see the changes made to the menu -Examples: +|`* * *` |restaurant owner |clear menu |revamp my menu when there is a need -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. +|`* * *` |restaurant owner |add a reservation |keep track of who booked a table in my restaurant -==== Step-by-step Instructions +|`* * *` |restaurant owner |edit a reservation |make changes when a customer requests to do so -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. +|`* * *` |restaurant owner |sort reservations |easily see the reservations in chronological order -**Main:** +|`* * *` |restaurant owner |delete a reservation |get rid of reservations that I don't need anymore -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +|`* *` |restaurant owner |check which dishes are not able to be cooked due to lack of ingredients |remove them from the daily menu -**Tests:** +|`* *` |lazy restaurant owner |save regular restocks and consumption data as the default |do not need to key in the same entries every time -. Add `RemarkCommandTest` that tests that `execute()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. +|`*` |forgetful restaurant owner |set reminders for the next restock |remember to restock -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` +|======================================================================= -**Main:** +[appendix] +== Use Cases -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. +(For all use cases below, the *System* is the `App` and the *Actor* is the `user`, unless +specified +otherwise) -**Tests:** +[discrete] +=== Use case: UC101 - Create account -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +*Pre-conditions*: -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. +* User has to be logged in. +* User must be an administrator. -**Main:** +*Guarantees*: -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. +* A new account will be created. -**Tests:** +*MSS* -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +1. User requests to create a new user account. +2. App create the new user account. +3. App returns a success message confirming that the user account has been created. ++ +Use case ends. -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +*Extensions* +[none] +* 2a. Username already exists. +[none] +** 2a1. App returns an error message. +** 2a2. User enters new data. ++ +Steps 2a1-2a2 are repeated until the data entered are correct. ++ +Use case resumes at step 3. +* 2b. Username or Password length not fulfilled. +[none] +** 2b1. App returns an error message. +** 2b2. User enters new data. ++ +Steps 2b1-2b2 are repeated until the data entered are correct. ++ +Use case resumes at step 3. -**Main:** +[discrete] +=== Use case: UC102 - Login -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +*Pre-conditions*: -**Tests:** +* User must not be logged in. -. Add test for `Remark`, to test the `Remark#equals()` method. +*Guarantees*: -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +* User will be logged into the App. -**Main:** +*MSS* -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) +1. User requests to login. +2. App authenticates the user. +3. App returns a success message confirming that the user has been logged in. ++ +Use case ends. -===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. +*Extensions* +[none] +* 2a. Credential is invalid. +[none] +** 2a1. App requests for the correct data. +** 2a2. User enters new data. ++ +Steps 2a1-2a2 are repeated until the data entered is correct. ++ +Use case resumes at step 3. -**Main:** +[discrete] +=== Use case: UC103 - Logout -. Add a new Xml field for `Remark`. +*Pre-conditions*: -**Tests:** +* User must be logged in. -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +*Guarantees*: -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +* User will be logged out of the App. -**Tests:** +*MSS* -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +1. User requests to logout. +2. App logs out the user. +3. App returns a success message confirming that the user has been logged out. ++ +Use case ends. -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +[discrete] +=== Use case: UC104 - Change password -**Main:** +*Pre-conditions*: -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +* User has to be logged in. -**Tests:** +*Guarantees*: -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +* Account data will remain intact if nothing changes OR +* Account data will be updated. -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +*MSS* -**Main:** +1. User requests to change password. +2. App edit the user account. +3. App returns a success message confirming that the user account has been edited. ++ +Use case ends. -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +*Extensions* +[none] +* 2a. New password is invalid. +[none] +** 2a1. App requests for the correct data. +** 2a2. User enters new data. ++ +Steps 2a1-2a2 are repeated until the data entered is correct. ++ +Use case resumes at step 3. -**Tests:** +[discrete] +=== Use case: UC201 - Add ingredient -. Update `RemarkCommandTest` to test that the `execute()` logic works. +*MSS* -==== Full Solution +1. User requests to add a new ingredient. +2. App adds the ingredient specified to the ingredient list. +3. App returns a success message confirming the new ingredient has been added. ++ +Use case ends. -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +*Extensions* -[appendix] -== Product Scope +[none] +* 1a. The user enters an invalid command format. +[none] +** 1a1. App returns a message telling user that the command format is invalid. +** 1a2. User requests to add ingredient again. ++ +Steps 1a1-1a2 are repeated until a valid command format is entered. ++ +Use case resumes at step 2. -*Target user profile*: +[none] +* 2a. The ingredient name entered is already in the ingredient list. +[none] +** 2a1. App returns a message telling user the ingredient name already exists. +** 2a2. User requests to add ingredient again. ++ +Steps 2a1-2a2 are repeated until an ingredient name which does not exist is entered. ++ +Use case resumes at step 3. -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +// tag::ucingredient[] +[discrete] +=== Use case: UC202 - Delete ingredient -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +*MSS* -[appendix] -== User Stories +1. User requests to list ingredients. +2. App shows a list of ingredients. +3. User requests to delete a specific ingredient by its index in the ingredient list, or the ingredient name. +4. App deletes the ingredient. ++ +Use case ends. -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +*Extensions* -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|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 +[none] +* 2a. The list is empty. ++ +Use case ends. -|`* * *` |user |add a new person | +[none] +* 3a. The given index or name is invalid. +[none] +** 3a1. App returns a message telling user the index or name entered is invalid. +** 3a2. User requests to delete ingredient again. ++ +Steps 3a1-3a2 are repeated until a valid index or name is entered. ++ +Use case resumes at step 4. -|`* * *` |user |delete a person |remove entries that I no longer need +[none] +* 3b. The ingredient name does not exist. +[none] +** 3b1. App returns a message telling user that the ingredient cannot be found. +** 3b2. User requests to delete ingredient again. ++ +Steps 3b1-3b2 are repeated until an existing ingredient name is entered. ++ +Use case resumes at step 4. -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +[none] +* 3c. The user enters an invalid format. +[none] +** 3c1. App returns a message telling user that the command format is invalid. +** 3c2. User requests to add ingredient again. ++ +Steps 3c1-3c2 are repeated until a valid command format is entered. ++ +Use case resumes at step 4. -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +[discrete] +=== Use case: UC203 - Edit ingredient -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +*MSS* -_{More to be added}_ +1. User requests to edit a specific ingredient. +2. App edits the specified ingredient with the updated values. +3. App returns a success message confirming the specified ingredient has been edited. ++ +Use case ends. -[appendix] -== Use Cases +*Extensions* + +[none] +* 1a. The given index or name is invalid. +[none] +** 1a1. App returns a message telling user the index or name entered is invalid. +** 1a2. User requests to edit ingredient again. ++ +Steps 1a1-1a2 are repeated until a valid index or name is entered. ++ +Use case resumes at step 2. + +[none] +* 1b. None of the optional fields are specified. ++ +[none] +** 1b1. App returns a message telling user at least one optional field has to be specified. +** 1b2. User requests to edit ingredient again. ++ +Steps 1b1-1b2 are repeated until at least one optional field is entered. ++ +Use case resumes at step 2. + +[none] +* 2a. The ingredient name does not exist. +[none] +** 2a1. App returns a message telling user that the ingredient cannot be found. +** 2a2. User requests to edit ingredient again. ++ +Steps 2a1-2a2 are repeated until an existing ingredient name is entered. ++ +Use case resumes at step 3. + +[discrete] +=== Use case: UC204 - Stock up ingredient + +*MSS* + +1. User requests to stock up a specific ingredient. +2. App updates the count of the specified ingredient. +3. App returns a success message confirming the specified ingredient has been stocked up. ++ +Use case ends. + +*Extensions* + +[none] + * 1a. The user enters an invalid format. +[none] + ** 1a1. App returns a message telling user that the command format is invalid. + ** 1a2. User requests to stock up ingredient again. + + Steps 1a1-1a2 are repeated until a valid command format is entered. + + Use case resumes at step 2. + +[none] + * 2b. The ingredient name does not exist. +[none] + ** 2b1. App returns a message telling user that the ingredient does not exist. + ** 2b2. User requests to stock up ingredient again. + + Steps 2b1-2b2 are repeated until a valid ingredient name is entered. + + Use case resumes at step 3. +// end::ucingredient[] + +// tag::ucmenu[] +[discrete] +=== Use case: UC301 - Add item to menu + +*MSS* + +1. User requests to add item to menu. +2. App adds the item to menu. +3. App returns a success message confirming the new item has been added. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. Invalid argument given for the command. ++ +[none] +** 1a1. App shows an error message that item name or/and item price are invalid. +** 1a2. User requests to add item to menu again. ++ +Steps 1a1-1a2 are repeated until valid item name and valid item price are entered. ++ +Use case resumes at step 2. + +* 1b. The item name entered is already in the menu. ++ +[none] +** 1b1. App shows an error message that the item name already exists. +** 1b2. User requests to add item to menu again. ++ +Steps 1b1-1b2 are repeated until item name that does not exist in the menu is entered. ++ +Use case resumes at step 2. + +[discrete] +=== Use case: UC302 - Delete item by index from menu + +*MSS* + +1. User requests to list items. +2. App shows a list of items in menu. +3. User requests to delete a specific item in menu. +4. App deletes the item. +5. App returns a success message confirming the specified item has been deleted. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given index is invalid. ++ +[none] +** 3a1. App shows an error message that the given index is invalid. +** 3a2. User requests to delete a specific item in menu again. ++ +Steps 3a1-3a2 are repeated until a valid index is entered. ++ +Use case resumes at step 4. +// end::ucmenu[] + +[discrete] +=== Use case: UC303 - Edit item from menu + +*MSS* + +1. User requests to list items. +2. App shows a list of items in menu. +3. User requests to edit a specific item in the list. +4. App edits the item with updated values. +5. App returns a success message confirming the specified item has been edited. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given index is invalid. ++ +[none] +** 3a1. App shows an error message that the given index is invalid. +** 3a2. User requests to delete a specific item in menu again. ++ +Steps 3a1-3a2 are repeated until a valid index is entered. ++ +Use case resumes at step 4. + +* 3b. None of the optional fields are specified. ++ +[none] +** 3b1. App shows an error message that none of the optional fields are specified. +** 3b2. User requests to edit a specific item in menu again. ++ +Steps 3b1-3b2 are repeated until one of the optional fields is entered. ++ +Use case resumes at step 4. + +[discrete] +=== Use case: UC304 - Give an item a discount + +*MSS* + +1. User requests to list items. +2. App shows a list of items in menu. +3. User requests to give a specific item in the list a discount. +4. App give the item a discount. +5. App returns a success message confirming the specified item has been given a discount. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given index is invalid. ++ +[none] +** 3a1. App shows an error message that the given index is invalid. +** 3a2. User requests to give a specific item in the list a discount again. ++ +Steps 3a1-3a2 are repeated until a valid index is entered. ++ +Use case resumes at step 4. + +* 3b. The given percentage is invalid. ++ +[none] +** 3b1. App shows an error message that the given percentage is invalid. +** 3b2. User requests to give a specific item in the list a discount again. ++ +Steps 3b1-3b2 are repeated until a valid percentage is entered. ++ +Use case resumes at step 4. + +// tag::ucreservations[] +[discrete] +=== Use case: UC401 - Add reservation + +*MSS* + +1. User requests to add a new reservation. +2. App adds the reservation to the reservations list. +3. App returns a success message confirming the new reservation has been added. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The reservation date or time entered has an incorrect format. +[none] +** 2a1. App returns a message telling user the date or time format is entered incorrectly. +** 2a2. User requests to add reservation again. ++ +Steps 2a1-2a2 are repeated until a proper date and time are entered. ++ +Use case resumes at step 3. -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +[discrete] +=== Use case: UC402 - Edit reservation + +*MSS* + +1. User requests to edit a specified reservation. +2. App edits the specified reservation with the updated values. +3. App returns a success message confirming the specified reservation has been edited. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given index is invalid. ++ +[none] +** 1a1. App returns a message telling user that the index is invalid. +** 1a2. User requests to edit reservation again. ++ +Steps 1a1-1a2 are repeated until a valid index is entered. ++ +Use case resumes at step 2. + +[none] +* 1b. None of the optional fields are specified. ++ +[none] +** 1b1. App returns a message telling user at least one optional field has to be specified. +** 1b2. User requests to edit reservation again. ++ +Steps 1b1-1b2 are repeated until at least one optional field is entered. ++ +Use case resumes at step 2. [discrete] -=== Use case: Delete person +=== Use case: UC403 - Delete reservation *MSS* -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to list reservations. +2. App shows a list of reservations. +3. User requests to delete a specific reservation in the list. +4. App deletes the reservation. + Use case ends. @@ -878,20 +1773,267 @@ Use case ends. * 3a. The given index is invalid. + [none] -** 3a1. AddressBook shows an error message. +** 3a1. App returns a message telling user the index is invalid. +** 3a2. User requests to delete reservation again. ++ +Steps 3a1-3a2 are repeated until a valid index is entered. ++ +Use case resumes at step 3. +// end::ucreservations[] + +// tag::ucsales[] +[discrete] +=== Use case: UC501 - Record sales volume of an item + +*Guarantees*: + +* A new sales record of an item will be appended to the record list. + +*MSS* + +1. User requests to record sales volume of an item for a specified day. +2. App computes the ingredients used and updates the ingredient list automatically. +3. App appends the record at the end of record list. +4. App returns a success message confirming that the recording is successful. ++ +Use case ends. + +*Extensions* +[none] +* 1a. Invalid command format entered by the user. +[none] +** 1a1. App returns a message telling user that the command format is invalid. +** 1a2. User requests to record sales volume again. ++ +Steps 1a1-1a2 are repeated until a valid command format is entered. ++ +Use case resumes at step 2. ++ + +[none] +* 1b. The given date or name or quantity sold or price is invalid. ++ +[none] +** 1b1. App returns a message telling user that the date or name or quantity sold or price is invalid. +** 1b2. User requests to record sales volume again. ++ +Steps 1b1-1b2 are repeated until all fields entered by the user are valid. ++ +Use case resumes at step 2. + +[none] +* 1c. Sales record of the same date and item name already exists in the record list. +[none] +** 1c1. App returns a message telling user that the item's record already exists. +** 1c2. User requests to record sales volume again. ++ +Steps 1c1-1c2 are repeated until a record with unique date and item name is entered. ++ +Use case resumes at step 2. ++ + +[none] +* 2a. One or more of the <> to update ingredient list were not satisfied. +[none] +** 2a1. App takes note of which criteria were not satisfied, and appends a notification message after the success +message. ++ +Use case resumes at step 3. ++ +// end::ucsales[] + + +[discrete] +=== Use case: UC502 - Editing sales record + +*Guarantees*: + +* Sales record will be updated to the user's input. + +*MSS* + +1. User requests to edit a sales record in the record list. +2. App updates the sales record to that given by the user. +3. App returns a success message confirming that the modification is successful. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. Invalid command format entered by the user. +[none] +** 1a1. App returns a message telling user that the command format is invalid. +** 1a2. User requests to edit sales record again. ++ +Steps 1a1-1a2 are repeated until a valid command format is entered. ++ +Use case resumes at step 2. ++ + +[none] +* 1b. Index specified is invalid. ++ +[none] +** 1b1. App returns a message telling user that the index specified is invalid. +** 1b2. User requests to edit sales record again. ++ +Steps 1b1-1b2 are repeated until a valid index is entered. ++ +Use case resumes at step 2. + +[none] +* 1c. None of the optional fields are specified. ++ +[none] +** 1c1. App returns a message telling user at least one optional field has to be specified. +** 1c2. User requests to edit sales record again. ++ +Steps 1c1-1c2 are repeated until at least one optional field is entered. ++ +Use case resumes at step 2. + +[none] +* 1d. The new date or name or quantity sold or price entered is invalid. ++ +[none] +** 1d1. App returns a message telling user that the date or name or quantity sold or price is invalid. +** 1d2. User requests to edit sales record again. ++ +Steps 1d1-1d2 are repeated until all fields entered by the user are valid. ++ +Use case resumes at step 2. + +[none] +* 1e. Sales record of the same date and item name as the new record already exists in the record list. +[none] +** 1e1. App returns a message telling user that the item's record already exists. +** 1e2. User requests to edit sales record again. ++ +Steps 1e1-1e2 are repeated until a unique record is entered. + Use case resumes at step 2. -_{More to be added}_ + +[discrete] +=== Use case: UC503 - Deleting sales record + +*MSS* + +1. User requests to delete a sales record in the record list. +2. App deletes the sales record. +3. App returns a success message confirming that the deletion is successful. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. Invalid command format entered by the user. +[none] +** 1a1. App returns a message telling user that the command format is invalid. +** 1a2. User requests to delete sales record again. ++ +Steps 1a1-1a2 are repeated until a valid command format is entered. ++ +Use case resumes at step 2. ++ + +[none] +* 1b. Index specified is invalid. ++ +[none] +** 1b1. App returns a message telling user that the index specified is invalid. +** 1b2. User requests to delete sales record again. ++ +Steps 1b1-1b2 are repeated until a valid index is entered. ++ +Use case resumes at step 2. + +[discrete] +=== Use case: UC504 - Displaying sales report + +*MSS* + +1. User requests to display the sales report of a specified date. +2. App generates the sales report. +3. App displays the sales report in a separate window. + ++ +Use case ends. + +*Extensions* + +[none] +* 1a. Invalid command format entered by the user. +[none] +** 1a1. App returns a message telling user that the command format is invalid. +** 1a2. User requests to display sales report again. ++ +Steps 1a1-1a2 are repeated until a valid command format is entered. ++ +Use case resumes at step 2. ++ + +* 1b. The given date is invalid. ++ +[none] +** 1b1. App returns a message telling user the date is invalid. +** 1b2. User requests to display sales report again. ++ +Steps 1b1-1b2 are repeated until a valid date is entered. ++ +Use case resumes at step 2. ++ + +* 2a. The generated sales report is empty. No record associated with the given date is found. ++ +[none] +** 2a1. App returns a message telling user no such record is found. +** 2a2. User requests to display sales report again with a new date. ++ +Steps 2a1-2a2 are repeated until a record with the given date is found. (i.e. Sales report generated is not empty.) ++ +Use case resumes at step 3. + +[discrete] +=== Use case: UC505 - Ranking items according to total revenue + +[NOTE] +The use cases for 1) ranking dates according to total revenue & 2) displaying sales chart are the same + +*MSS* + +1. User requests to rank items according to the total revenue accumulated in past sales records. +2. App generates the ranking. +3. App displays the ranking in a separate window. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. Record list is empty. No record has ever been added. +[none] +** 2a1. App returns a message telling user that the record list is empty. ++ +Use case ends. [appendix] == Non Functional Requirements -. Should work on any <> as long as it has Java `9` or higher installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +. Should work on any <> as long as it has Java `9` or higher installed. +. Respond fast to user input and show the respective output within milliseconds. . 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}_ +. System must be secured such that only authorised employees can access it and execute commands. +. System should not require constant maintenance and work off-the-shelf without any installation. +. JAR file should not exceed 50 MB. +. Test coverage should be at least 80%. +. Should always favour security over efficiency in development. +. Any user who has the basic proficiency of the English language should be able to use the application with the help + of the link:{repoURL}/docs/UserGuide.adoc[`UserGuide.adoc`]. +. System should not require any internet access. +. Only the English language will be used. [appendix] == Glossary @@ -899,25 +2041,17 @@ _{More to be added}_ [[mainstream-os]] Mainstream OS:: Windows, Linux, Unix, OS-X -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others - [appendix] -== Product Survey - -*Product Name* +== Acronyms +[[gui]] GUI:: +*Graphical User Interface* allows the use of icons or other visual indicators to interact with electronic devices, +rather than using only text via the command line. -Author: ... +[[uml]] UML:: +Unified Modeling Language is used to specify, visualize, construct and document the artifacts of software systems. -Pros: - -* ... -* ... - -Cons: - -* ... -* ... +[[mss]] MSS:: +Main Success Scenario describes the most straightforward interaction for a given use case, which assumes that nothing goes wrong. [appendix] == Instructions for Manual Testing @@ -925,42 +2059,331 @@ Cons: Given below are instructions to test the app manually. [NOTE] -These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. +These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing === Launch and Shutdown . Initial launch -.. Download the jar file and copy into an empty folder -.. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. +.. Download the `jar` file and copy into an empty folder. +.. Double-click the `jar` file. + + *Expected*: Shows the GUI with a set of sample data. The window size may not be optimum. . Saving window preferences .. Resize the window to an optimum size. Move the window to a different location. Close the window. -.. Re-launch the app by double-clicking the jar file. + - Expected: The most recent window size and location is retained. - -_{ more test cases ... }_ - -=== Deleting a person - -. Deleting a person while all persons are listed - -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. -.. 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. -.. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + - Expected: Similar to previous. - -_{ more test cases ... }_ +.. Re-launch the app by double-clicking the `jar` file. + + *Expected*: The most recent window size and location is retained. + +=== Accounts Management + +. Login +.. Prerequisite: The account must exists in the accounts record. The `root` account exists by default. +.. Test case: `login id/root pw/1122qq` + + *Expected*: User will be logged in to the `root` account, and the username will be set accordingly at the + top-right corner of the GUI. +.. Test case: `login pw/1122qq id/root` + + *Expected*: Same as previous as the parameters can be supplied in any order. +.. Test case: `login id/helloworld pw/lalala` + + *Expected*: Account does not exists and user will be shown an error message. +.. Test case: `login id/root pw/` + + *Expected*: User will be prompted to provide a valid password. +.. Test case: `login id/ pw/1122qq` + + *Expected*: User will be prompted to provide a valid username. +.. Test case: `login` + + *Expected*: User will be prompted to conform to the command parameters by providing all necessary fields with + valid values. + +. Logout +.. Prerequisite: User must be logged in first. +.. Test case: `logout` + + *Expected*: User will be logged out, and the username will be reset back to `Guest` at the top-right corner of the + GUI. + +. Changing password +.. Prerequisite: User must be logged in. The old and new password can be the same. +.. Test case: `change-password npw/newpassword` + + *Expected*: User's password will be updated. The new password must be used for future login. +.. Test case: `change-password npw/newpassword npw/evennewerpassword` + + *Expected*: `evennewerpassword` will be recorded as the new password. +.. Test case: `change-password npw/` + + *Expected*: User will be prompted to provide a valid password. +.. Test case: `change-password` + + *Expected*: User will be prompted to conform to the command parameters by providing all necessary fields with + valid values. + +. Registering a new account +.. Prerequisite: User must be logged in, and the account must exists in the accounts record. _Currently, any user will +be able to register a new account as the user role feature will only be implemented in `v2.0`. See +<> for more information_ +.. Test case: `register id/johndoe pw/1122qq n/John Doe` + + *Expected*: A new account with the id `johndoe` will be created. Details of `johndoe` will be shown. The timestamp + of the status bar will be updated as well. +.. Test case: `register id/john doe pw/1122qq n/` + + *Expected*: User will be prompted to provide a valid name. +.. Test case: `register id` + + *Expected*: User will be prompted to conform to the command parameters by providing all necessary fields with + valid values. +.. Other incorrect attempts: `register id/ pw/1122qq n/John Doe`, `register id/johndoe pw/1122 qq n/John Doe` + + *Expected*: User will be prompted with a relevant error message to provide a valid value for the field that has an + error. + +. Deregistering an account +.. Prerequisite: User must be logged in, and the account must exists in the accounts record. +.. Test case: `deregister id/johndoe` + + *Expected*: Account with the id `johndoe` will be deregistered (i.e. deleted). +.. Test case: `deregister id/` + + *Expected*: User will be prompted an error message that they are not allowed to deregister their own account. +.. Test case: `deregister` + + *Expected*: User will be prompted to conform to the command parameters by providing all necessary fields with + valid values. + +. Finding account(s) +.. Prerequisite: User must be logged in. +.. Test case: `find-account ` + + *Expected*: List all accounts that contain the string `ro` in its username. +.. Test case: `find-account` + + *Expected*: User will be prompted to conform to the command parameters by providing all necessary fields with + valid values. + +. Selecting an account +.. Prerequisite: User must be logged in. +.. Test case: `select-account 1` + + *Expected*: The account at index 1 of the list will be selected. +.. Other incorrect attempts: `select-account` or `select-account -1` or `select-account alphabet` + + *Expected*: User will be prompted to conform to the command parameters by providing all necessary fields with + valid values. + +. Listing accounts +.. Prerequisite: User must be logged in. +.. Test case: `list-accounts` + + *Expected*: The list of accounts will be shown at the panel on the left of the GUI. + +=== Ingredient Management +. Adding an ingredient + +.. Pre-requisites: The ingredient must not have the same name as another existing ingredient in the ingredient list +and must be logged in. +.. Test case: `add-ing n/Chicken Thigh u/kilogram p/14.00 m/20` + + *Expected*: Ingredient is added into the ingredient list. Details of the added ingredient is shown in the + status message. Timestamp in the status bar is updated. +.. Test case: add-ing n/ + + *Expected*: No ingredient is added. Error details are shown in the status message. Status bar remains the + same. +.. Other incorrect `add-ing` commands to try: `add-ing`, `add-ing n/123 u/kilogram p/14.00 m/20`, +`add-ing n/Chicken Thigh u/+++ p/14.00 m/20` + + *Expected*: Similar to previous. + +. Listing all ingredients + +.. Pre-requisites: Must be logged in. +.. Test case: `list-ing` + + *Expected*: All ingredients are listed at the panel on the left of the UI. + +. Listing all the items that are low on stock + +.. Pre-requisites: Must be logged in. +.. Test case: `low-stock` + + *Expected*: All ingredients that have a number of units less than its minimum value are listed. + +. Deleting an ingredient + +.. Pre-requisites: At least one ingredient in the ingredient list and must be logged in. +.. Test case: `delete-ing 1` + + *Expected*: The first ingredient in the displayed ingredient list is deleted. Details of the deleted ingredient is + shown in the status message. Timestamp in the status bar is updated. +.. Test case: `delete-ing Chicken Thigh` + + *Expected*: The ingredient with the specified name is deleted. Details of the deleted ingredient is + shown in the status message. Timestamp in the status bar is updated. +.. Test case: `delete-ing 0` + + *Expected*: No ingredient is deleted. Error details are shown in the status message. Status bar + remains the same. +.. Other incorrect `delete-ing` commands to try: `delete-ing`, `delete-ing x` (where x is larger + than the ingredient list size) + + *Expected*: Similar to previous. + +. Editing an ingredient + +.. Pre-requisites: The ingredient to be edited must be an existing entry in the ingredient list and must be logged in. +The edited ingredient must not have the same name as another ingredient existing in the ingredient list. +.. Test case: `edit-ing 1 n/Chicken Drumstick` + + *Expected*: Edited ingredient replaces the existing ingredient in the ingredient list. Details of the edited + ingredient is shown in the status message. Timestamp in the status bar is updated. +.. Test case: `edit-ing Chicken Thigh n/Chicken Drumstick` + + *Expected*: Similar to previous. +.. Test case: `edit-ing n/` + + *Expected*: No ingredient is edited. Error details are shown in the status message. Status bar + remains the same. +.. Other incorrect `edit-ing` commands to try: `edit-ing`, `edit-ing Chicken Drumstick+` + + *Expected*: Similar to previous. +. Stocking up an ingredient or multiple ingredients + +.. Pre-requisites: The ingredient(s) to be stocked up must be an existing entry or existing entries in the ingredient +list and must be logged in. +.. Test case: `stockup n/Chicken Drumstick nu/10` + + *Expected*: The number of units of the ingredient increases by the specified number. The name of the + ingredient being restocked is shown in the status message. Timestamp in the status bar is updated. +.. Test case: `stockup n/Chicken Drumstick nu/10 n/Cheese nu/15` + + *Expected*: Similar to previous. +.. Test case: `stockup n/Chicken Drumstick` + + *Expected*: No ingredient is stocked up. Error details are shown in the status message. Status + bar remains the same. +.. Other incorrect `stockup` commands to try: `stockup`, `stockup n/Chicken Drumstick n/Cheese nu/10 nu/20` + + *Expected*: Similar to previous. + +. Selecting an ingredient + +.. Pre-requisites: At least one ingredient in the ingredient list and must be logged in. +.. Test case: `select-ing 1` + + *Expected*: The first ingredient in the displayed ingredient list is selected. Details of the selected ingredient is + shown in the right panel of the UI. +.. Test case: `select-ing 0` + + *Expected*: No ingredient is selected. Error details are shown in the status message. +.. Other incorrect `select-ing` commands to try: `select-ing`, `select-ing x` (where x is larger + than the ingredient list size) + *Expected*: Similar to previous. + +// tag::testMenu[] +=== Menu Management + +. Adding a new item with valid name, price and tag +.. Prerequisites: You must not have the item, specified in the test case below, in the menu. +.. Test case: `add-item n/Chicken Rice p/3 t/rice t/chicken` + + *Expected*: Item is added to the menu. Details of the item shown in status message. +.. Incorrect commands to try: `add-item n/test p/string`, `add-item n/@*@^&@* p/3` + +. Editing an item with valid name, price and tag +.. Prerequisites: You must have at least 1 item in the menu. +.. Test case: `edit-item 1 n/Duck Rice p/3 t/rice t/duck` + + *Expected*: Item specified by the index is updated with new details. New details of the item shown in status message. +.. Incorrect commands to try: `edit-item 1 n/test p/string`, `edit-item n/name p/3` + +. Deleting an item by index +.. Prerequisites: You must have at least 1 item in the menu. +.. Test case: `delete-item-index 1` + + *Expected*: Item specified by the index is deleted from the menu. Number of items deleted shown in status message. +.. Incorrect commands to try: `delete-item-index -2`, `delete-item-index string` + +. Deleting an item by name +.. Prerequisites: You must have the item, specified in the test case below, in the menu. +.. Test case: `delete-item-name chicken rice` + + *Expected*: Item specified by name is deleted from the menu. Details of the deleted item shown in status message. +.. Incorrect commands to try: `delete-item-name @*@^&@*` +// end::testMenu[] + +. Listing all items +.. Test case: `list-items` + + *Expected*: List all items in the menu. + +. Clearing menu +.. Test case: `clear-menu` + + *Expected*: Delete all items in the menu. + +. Selecting an item +.. Prerequisites: You must have at least 1 item in the menu. +.. Test case: `select-item 1` + + *Expected*: Selects the item specified by the index in the menu. Index of the selected item shown in status message. +.. Incorrect commands to try: `select-item -2`, `select-item string` + +. Locating items by name +.. Prerequisites: You must have at least 1 item with name matching the keyword specified in the test case below. +.. Test case: `find-item rice` + + *Expected*: Finds the item with name matching the keyword in the menu. Number of matched items shown in status message. + +. Locating items by tag +.. Prerequisites: You must have at least item with tag matching the keyword specified in the test case below. +.. Test case: `filter-menu duck` + + *Expected*: Finds the item with tag matching the keyword in the menu. Number of matched items shown in status message. + +. Sorting the menu by name +.. Test case: `sort-menu name` + + *Expected*: Sorts the menu by name. Sorting method shown in status message. + +. Sorting the menu by price +.. Test case: `sort-menu price` + + *Expected*: Sorts the menu by price. Sorting method shown in status message. + +. Giving discount to all items in the displayed item list +.. Prerequisites: You must have at least 1 item in the menu. +.. Test case: `discount-item ALL dp/20` + + *Expected*: Give all items in the displayed item list a discount. Number of items given a discount shown in status message. +.. Incorrect commands to try: `discount-item -2 dp/20`, `discount-item string dp/20` + +. Adding a recipe to an item +.. Prerequisites: You must have at least 1 item in the menu. +.. Test case: `recipe-item 1 r/some recipe` + + *Expected*: Adds the recipe to the item specified by the index in the menu. Details of specified item shown in + status message. + +. Adding required ingredients to an item +.. Prerequisites: You must have at least 1 item in the menu. +.. Test case: `add-required-ingredients 1 n/chicken nu/3 n/rice nu/3` + + *Expected*: Adds the required ingredients to the item specified by the index in the menu. Details of specified item shown in status message. +.. Incorrect commands to try: `add-required-ingredients -2 n/str nu/3`, `add-required-ingredients 1 n/str nu/-3`, +`add-required-ingredients 1 n/str n/other nu/3 nu/3` + +// tag::testreservations[] +=== Reservations Management + +. Adding a reservation + +.. Prerequisites: The reservation to be added must not be a duplicated entry in the reservations list and the user must be + logged in. +.. Test case: `add-reservation n/John Doe px/4 d/05-12-2019 ti/10:00 t/Driving` + + *Expected*: Reservation will be added into the reservation list. Details of the added reservation will be shown. +.. Test case: `add-reservation n/` + + *Expected*: Reservation will not be added. Improper format error details shown. +.. Test case: `add-reservation n/John Doe px/4 d/01-01-2018 ti/10:00 t/Driving` + + *Expected*: Reservation will not be added. "Date should not have passed" error will be shown. +.. Other incorrect add-reservation commands to try: `add-reservation`, `add-reservation n/John Doe px/4@ d/05-12-2019 ti/10:00` + + *Expected*: Reservation will not be added. Improper format error details shown. + +. Listing reservations +.. Prerequisites: The user must be logged in. +.. Test case: `list-reservations` + + *Expected*: The reservations list will be shown. + +. Selecting a reservation +.. Prerequisites: The reservations list must contain at least 1 reservation. +.. Test case: `select-reservation 1` + + *Expected*: Selects the reservation specified by the index in the reservations list. Index of the selected + reservation will be shown. +.. Incorrect commands to try: `select-reservation`, `select-reservation all` + +. Editing a reservation +.. Prerequisites: The reservation to be edited must be an existing entry in the reservations list and the user must +be logged in. +.. Test case: `edit-reservation 1 d/06-10-2020` + + *Expected*: Edited reservation will replace the existing reservation in the reservations list. Details of the + edited reservation will be shown. +.. Test case: `edit-reservation n/` + + *Expected*: No reservation is edited. Improper format error details shown. +.. Other incorrect edit-reservation commands to try: `edit-reservation` , `edit-reservation 1 px/4@` + + *Expected*: Similar to previous. + +. Deleting a reservation +.. Prerequisites: The reservation to be deleted must be an existing entry in the reservations list and the user must +be logged in. +.. Test case: `delete-reservation 1` + + *Expected*: First reservation will be deleted from the list. Details of the deleted reservation will be shown. +.. Test case: `delete-reservation 0` + + *Expected*: No reservations are deleted. Improper format error details shown. +.. Other incorrect delete-reservation commands to try: `delete-reservation`, `delete-reservation all` + + *Expected*: Similar to previous. + +. Sorting reservations +.. Prerequisites: The user must be logged in and the reservations list must be populated with more than one entry. + +(and preferably be initially unsorted, to see the effects of the `sort-reservations` command) +.. Setup: Enter these commands to populate the reservations list with an unsorted set of reservations. +... `add-reservation n/Mandelbrot px/3 d/06-12-2019 ti/18:00` +... `add-reservation n/Benoit px/1 d/05-12-2019 ti/10:00` +... `add-reservation n/B px/2 d/05-12-2019 ti/12:00` +.. Test case: `sort-reservations` + + *Expected*: Reservations list will be sorted by Date/Time in ascending order. +// end::testreservations[] === Saving data - . Dealing with missing/corrupted data files - -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ - -_{ more test cases ... }_ +.. To simulate missing/corrupted data file, simply go to the `data` folder, open up `restaurantbook.xml` and remove +any XML tags, or simply delete the `restaurantbook.xml` file. The next time the application is launched, a clean +`restaurantbook.xml` will be re-generated. diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 7e0070e12f49..28f450f7b6fa 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += MeNUS - User Guide :site-section: UserGuide :toc: :toc-title: @@ -11,20 +11,63 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: +:warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2103-AY1819S1-F10-4/main/tree/master +:toclevels: 3 -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team CS2103-AY1819S1-F10-4`      Since: `Sep 2018`      Licence: `MIT` -== Introduction +// tag::intro[] +== Welcome to MeNUS +link:{repoURL}[MeNUS] empowers you to improve the **m**anagement and its **e**fficiency of your restaurants in **NUS**. -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +As long as you feel that you are using too many complex and expensive systems to manage your restaurants, +link:{repoURL}[MeNUS] is here to help you and it is the only restaurant management system you will ever need. + +Introducing link:{repoURL}[MeNUS], a revolutionary restaurant management desktop application that is *optimized for +users who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User +Interface (GUI)*. If you can type fast, link:{repoURL}[MeNUS] allows you to manage core aspects of your restaurants -- +accounts (i.e. employees), menus, ingredients, sales and reservations management -- all within a single application! + +=== Legend +The following *3* callouts will be used throughout the documentation which you may wish to pay attention to as it may +contain important details: + +[NOTE] +Just for your info, do not be alarmed. Be sure to read these notes as it might contain some important information. + +[TIP] +Perhaps something can be done using another approach, but it is up to you to decide. Tips are often not important and + can be safely ignored. + +[WARNING] +Some things might go wrong if you are not careful, or did not follow the instructions correctly. You are strongly +advised to read whatever is in this block. + +[[user-interface]] +=== A Quick Look at MeNUS + +*MeNUS*’s user interface is split into a few sections. These sections are highlighted in the image below. + +image::UserInterfaceHighlight.png[width="1000"] + +Let's get started! +// end::intro[] == Quick Start +Follow this installation guide to get *MeNUS* up and running on your computer. . Ensure you have Java version `9` or later installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. + +[NOTE] +==== +* If you are unsure which Java version is installed, you may refer to this link:https://www.java.com/en/download/help/version_manual.xml[link]. +* You may install the current version of Java link:https://www.oracle.com/technetwork/java/javase/downloads/index.html[here]. +==== + +. Download the latest `menus.jar` link:https://github.com/CS2103-AY1819S1-F10-4/main/releases[here]. +. Copy the file to the folder you want to use as the home folder. . Double-click the file to start the app. The GUI should appear in a few seconds. + image::Ui.png[width="790"] @@ -32,229 +75,1230 @@ image::Ui.png[width="790"] . Type the command in the command box and press kbd:[Enter] to execute it. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. . Some example commands you can try: +* **`help`** : Opens up the help page +* **`login`**`id/root pw/1122qq` : Logs in to the root account +* **`add-item`**`n/Apple p/2.00 t/fruit` : Creates a new item in the menu +* *`exit`* : Exits the application + +. Refer to <> for details of each command. + +[[Components]] +== Components +*MeNUS* consists of five core components: accounts (i.e. employees), menus, ingredients, sales and reservations +management. + +=== Accounts +image:account.png[width="50"] + +* You can create an account for each of your employee to manage the application on your behalf, which gives them +access to the other core components. + +=== Menus +image:menu.png[width="50"] + +You can manage the menu by adding, editing or remove items from the menu. + +=== Ingredients +image:ingredient.png[width="50"] + +You can keep track of the ingredient availability in your restaurant. -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list -* *`exit`* : exits the app +=== Sales +image:sales.png[width="50"] + +The built-in Sales Management component in MeNUS provides you with the tools you will need to keep track of financial + records efficiently. Several analytical features are also incorporated to assist you in financial decision-making + and devising marketing strategies. -. Refer to <> for details of each command. +=== Reservations +image:reservation.png[width="50"] + +You can keep track of customer reservations on the system. [[Features]] -== Features +== Key Features +=== Convenience +*MeNUS* is an integrated application that will provide you with the utmost convenience and tools you will need to +manage your restaurants. It allows you to: +* Export data to `.xml` (default) or Excel file `[coming in v2.0]`. + +=== Security +We understand that digital security is your biggest concern. *MeNUS* is capable of securing your restaurants' data by: + +* Encrypting all data using state of the art encryption scheme `[coming in v2.0]`. +* Requiring users to be authenticated before they can execute any commands. +* Providing accountability through logging of system events. + +=== Efficiency +Time is money. *MeNUS* ensures that the application will: + +* Load within 5 seconds. +* Execute commands within split of a second and update the GUI almost instantaneously. + +[[Commands]] +== Commands +*MeNUS* is jam-packed with features and it may be daunting for new users. The subsequent sections of the user guide +provides a step by step walk-through of all the commands *MeNUS* has to offer. + +Do read our short explanation about Command Format below so that the subsequent portions of this section will make sense to you. ==== *Command Format* -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add-item n/ITEM_NAME`, `ITEM_NAME` is a +parameter which can be used as `add-item n/Burger`. +* Items in square brackets are optional e.g `n/ITEM_NAME [t/TAG]` can be used as `n/Burger t/beef` or as `n/Burger`. +* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as +`{nbsp}` (i.e. 0 times), `t/Italian`, `t/Italian t/Wednesday` etc. +* Parameters can be in any order e.g. if the command specifies `n/ITEM_NAME p/ITEM_PRICE`, `p/ITEM_PRICE n/ITEM_NAME` + is also acceptable. +==== + +As this is a management system, only the following commands can be executed without being authenticated (i.e. as guest): + ==== +`help`, `login`, `select-item`, `find-item`, `filter-menu`, `list-items`, `today-special`, `exit` +==== + +=== General +The commands in this section does not tie to any of the 5 components. -=== Viewing help : `help` +==== Viewing help : `help` or `h` +Opens up the help window. Very useful if you are a new user. + +Format: `help` or `h` + +==== Listing entered commands : `history` or `hist` +Lists all the commands that you have entered in reverse chronological order. + +Format: `history` or `hist` +[NOTE] +==== +Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box +==== -Format: `help` +// tag::undoredo[] +==== Undoing previous command : `undo` or `u` -=== Adding a person: `add` +Restores the restaurant book to the state before the previous _undoable_ command was executed. + +Format: `undo` or `u` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +[NOTE] +==== +Undoable commands: those commands that modify the restaurant book's contents +==== -[TIP] -A person can have any number of tags (including 0) +Examples: + +* `register id/root pw/1122qq` `n/Ang Zhi Kai` + +`undo` (reverses the `register id/root pw/1122qq n/Ang Zhi Kai` command) + + +* `undo` + +The `undo` command fails as there are no undoable commands executed previously. + +* `deregister id/root` + +`clear` + +`undo` (reverses the `clear` command) + +`undo` (reverses the `deregister id/root` command) + + +==== Redoing the previously undone command : `redo` or `r` + +Reverses the most recent `undo` command. + +Format: `redo` or `r` + +Examples: + +* `delete-item-index 1` + +`undo` (reverses the `delete-item-index 1` command) + +`redo` (reapplies the `delete-item-index 1` command) + + +* `delete-item-index 1` + +`redo` + +The `redo` command fails as there are no `undo` commands executed previously. + +* `delete-item-index 1` + +`clear` + +`undo` (reverses the `clear` command) + +`undo` (reverses the `delete-item-index 1` command) + +`redo` (reapplies the `delete-item-index 1` command) + +`redo` (reapplies the `clear` command) + + +[NOTE] +Undo and redo commands do not refresh the detailed panel +// end::undoredo[] + +==== Clearing all entries : `clear` or `c` + +Clears all entries from the restaurant book. + +Format: `clear` or `c` + +==== Saving the data + +Restaurant book data are saved in the hard disk automatically after any command that changes the data. No manual +saving is required. + +==== Exiting the program : `exit` or `ex` + +Exits the program. + +Format: `exit` or `ex` + +// tag::accountmanagement[] +=== Accounts Management +==== Logging in: `login` + +Logs into an existing account. + +Format: `login id/USERNAME pw/PASSWORD` + +Examples: + +* `login id/root pw/1122qq` + +[NOTE] +==== +To testers: You may access the application using the default `root` account: `login id/root pw/1122qq` +==== + +==== Logging out: `logout` + +Logs out of the account. + +Format: `logout` + +[NOTE] +==== +History will automatically be cleared upon logging out +==== + +==== Creating user account: `register` or `reg` + +Creates a new user account. + +Format: `register id/USERNAME pw/PASSWORD n/FULL_NAME` or `reg id/USERNAME pw/PASSWORD n/FULL_NAME` + +[NOTE] +==== +This command will be improved in the future to support user role (i.e. privilege system). See <, +Section 5.7.1, “Improving registration [coming in v2.0]”>> for more information +==== + +Examples: + +* `register id/azhikai pw/1122qq n/Ang Zhi Kai` +* `reg id/azhikai pw/1122qq n/Ang Zhi Kai` + +==== Changing user password: `change-password` or `cp` + +Edits the password of the current logged in user account. + +Format: `change-password npw/NEW_PASSWORD` or `cp npw/NEW_PASSWORD` 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` +* `change-password npw/1122qq` +* `cp npw/1122qq` -=== Listing all persons : `list` +==== Deleting user account: `deregister` or `dereg` -Shows a list of all persons in the address book. + -Format: `list` +Deletes an existing user account. + +Format: `deregister id/USERNAME` or `dereg id/USERNAME` -=== Editing a person : `edit` +[NOTE] +==== +This command will only be executable by a highly privileged user in the future. See <, +Section 5.7.2, “Creating user role [coming in v2.0]”>> for more information +==== -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Examples: + +* `deregister id/azhikai` +* `dereg id/azhikai` + +==== Select user account: `select-account` or `sa` +Selects the account identified by the index number. + +Format: `select-account INDEX` or `sa INDEX` **** -* 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. +* The index refers to the index number shown in the displayed item list +* The index *must be a positive integer* `1, 2, 3, ...` +**** + +[NOTE] +==== +Selecting the account does not render any data on the detailed panel due to the simplicity of the data itself +==== + +Examples: + +* `list-accounts` + +`select-account 2` + +Selects the 2nd account in the list. +* `fa azhikai` + +`sa 1` + +Selects the 1st account in the results of the `find-account` command. + +==== Locating account by username: `find-account` or `fa` + +Finds account whose username contains the keyword. + +Format: `find-account KEYWORD` or `fa KEYWORD` + +**** +* The search is case insensitive. e.g `Root` will match `root` +* Only the username is searched +* Only one keyword is allowed since `username` does not contain spaces +* Full keyword is not necessary; e.g. `roo` and `root` will match `root` +**** + +Examples: + +* `find-account root` or `fa root` + +Return any accounts whose username contains the string `root` + +==== List user accounts: `list-accounts` or `la` + +List all user accounts. + +Format: `list-accounts` or `la` + +[WARNING] +==== +Passwords are not and should not be displayed +==== +// end::accountmanagement[] + +// tag::ingredientmanagement[] +=== Ingredients Management +This section explains how you may use the commands relating to the Ingredient Management feature. + +[[add-ingredient]] +==== Adding an ingredient: `add-ingredient` or `add-ing` + +Adds a new ingredient to the ingredient list. + +Format: `add-ingredient n/INGREDIENT_NAME u/UNIT_TYPE p/PRICE_PER_UNIT m/MINIMUM` + +[NOTE] +==== +* MINIMUM refers to the number of units below which an ingredient will be considered low in stock count +* When a new ingredient is first added, its number of units is set to zero +==== + +Examples: + +* `add-ingredient n/cod fish u/kilogram p/20 m/1` + +Adds the ingredient `cod fish` with a price of `$20/kilogram` and minimum threshold of `1 unit`. + +[[list-ingredients]] +==== Listing all ingredients : `list-ingredients` or `list-ing` + +Shows a list of all ingredients in the ingredient list. + +Format: `list-ingredients` + +[[low-stock]] +==== Listing ingredients with low stock count: `low-stock` + +Shows a list of ingredients that are low in stock count. + +Format: `low-stock` + +[NOTE] +==== +An ingredient is low in stock count when the number of units available is less than its minimum threshold +==== + +[[delete-ingredient]] +==== Removing an ingredient: `delete-ingredient` or `delete-ing` + +Deletes the specified ingredient from the ingredient list. + +Format: `delete-ingredient INDEX` or `delete-ingredient INGREDIENT_NAME` + +[NOTE] +==== +* Deletes the ingredient at the specified `INDEX` +* `INDEX` refers to the index number shown in the displayed ingredient list +* `INDEX` *must be a positive integer* 1, 2, 3, ... +* Alternatively, deletes the ingredient with the specified `INGREDIENT_NAME` +==== + +Examples: + +* `list-ingredients` + +`delete-ingredient 1` + +`list-ingredients` + +Deletes the 1st ingredient in the ingredient list. + +* `delete-ingredient cod fish` + +`list-ingredients` + +Deletes the ingredient `cod fish` from the ingredient list. + +[[edit-ingredient]] +==== Editing an ingredient: `edit-ingredient` or `edit-ing` + +Edits an ingredient in the ingredient list. + +Format: `edit-ingredient INDEX [n/INGREDIENT_NAME] [u/UNIT_TYPE] [p/PRICE_PER_UNIT] [m/MINIMUM]` or `edit-ingredient +on/ORIGINAL_INGREDIENT_NAME [n/NEW_INGREDIENT_NAME] [u/UNIT_TYPE] [p/PRICE_PER_UNIT] [m/MINIMUM]` + +[NOTE] +==== +* Edits the ingredient at the specified `INDEX`. The index refers to the index number shown in the displayed ingredient list. The index *must be a positive integer* 1, 2, 3, ... +* Existing values will be updated to the input values +* Alternatively, edits the ingredient with the specified `ORIGINAL_INGREDIENT_NAME` +* If both `INDEX` and `ORIGINAL_INGREDIENT_NAME` are specified and valid, the edit will be done according to `INDEX` +==== + +[WARNING] +==== +At least one of the optional fields must be provided! +==== + +Examples: + +* `edit-ingredient 3 n/thin fries` + +Edits the name of the 3rd ingredient to `thin fries`. + +* `edit-ingredient 4 u/1.5ml bottle p/1.20` + +Edits the unit type and price per unit of the 4th ingredient to `1.5ml bottle` and `1.20` respectively. + +* `edit-ingredient on/ketchup n/tomato ketchup` + +Edits the name of `ketchup` to `tomato ketchup`. + +[[stockup]] +==== Stocking up an ingredient: `stockup` + +Increases the number of units of an ingredient or multiple ingredients. + +Format: `stockup n/INGREDIENT_NAME... nu/NUMBER_OF_UNITS...` + +[WARNING] +==== +* `NUMBER_OF_UNITS` for an ingredient must follow the `INGREDIENT_NAME` for that particular ingredient +* If the same ingredient is specified more than once, the last `NUMBER_OF_UNITS` will supercede +==== + +Examples: + +* `stockup n/cod fish nu/5` + +Stocks up `5 units` of `cod fish`. +* `stockup n/chicken thigh nu/10 n/fries nu/20 n/tomato ketchup nu/50` + +Stocks up `10 units` of `chicken thigh`, `20 units` of `fries` and `50 units` of `tomato ketchup`. + +[[select-ingredient]] +==== Selecting an ingredient: `select-ingredient` or `select-ing` + +Selects the ingredient identified by the index number used in the displayed ingredient list. + +Format: `select-ingredient INDEX` or `select-ing INDEX` + +[NOTE] +==== +* Selects the ingredient and displays the details of the ingredient at the specified `INDEX` +* The index refers to the index number shown in the displayed ingredient list +* The index *must be a positive integer* `1, 2, 3, ...` +==== + +Examples: + +* `list-ingredients` + +`select-ingredient 2` + +Selects the 2nd ingredient in the displayed ingredient list. +* `low-stock` + +`select-ingredient 1` + +Selects the 1st ingredient in the results of the `low-stock` command. +// end::ingredientmanagement[] + +// tag::menuPart1[] +=== Menu Management +[[add-item]] +==== Adding an item to the menu: `add-item` or `ai` + +Adds an item to the menu + +Format: `add-item n/ITEM_NAME p/ITEM_PRICE [t/TAG]...` + +**** +* `ITEM_NAME` and `ITEM_PRICE` must be provided +* An item can have any number of tags (including 0) +* `ITEM_NAME` should only contain alphanumeric characters and spaces, and it should not be blank +* `ITEM_PRICE` should only contain numbers(no negative), at most 2 decimal place and smaller than or equals to +2,147,483,647 +* You cannot add items with the same name **** 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. +* `add-item n/Burger p/2` +* `ai n/Burger Set p/4.5 t/Set` -=== Locating persons by name: `find` +==== Editing an item in the menu : `edit-item` or `ei` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Edits an existing item in the menu. + +Format: `edit-item INDEX [n/ITEM_NAME] [p/ITEM_PRICE] [t/TAG]...` **** -* 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` +* Edits the item at the specified `INDEX`. The index refers to the index number shown in the displayed item 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 item will be removed i.e adding of tags is not cumulative +* You can remove all the item's tags by typing `t/` without specifying any tags after it +* Editing item price will remove discount given by `discount-item` command **** Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `edit-item 1 n/burger p/3` + +Edits the name and price of the 1st item to be `burger` and `3` respectively. +* `ei 2 p/4 t/` + +Edits the price of the 2nd item to be `4` and clears all existing tags. -=== Deleting a person : `delete` +==== Deleting an item by index: `delete-item-index` or `dii` -Deletes the specified person from the address book. + -Format: `delete INDEX` +Deletes the specified item from the menu. + +Format: `delete-item-index INDEX [ei/INDEX]` **** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. +* Deletes the item(s) specified by `INDEX` +* The index refers to the index number shown in the displayed item list * The index *must be a positive integer* 1, 2, 3, ... +* You can delete a range of items by adding the ending index `ei\INDEX` +* The ending index can not be smaller than starting index +**** + +Examples: + +* `list-items` + +`delete-item-index 2` + +Deletes the 2nd item in the menu. +* `fi Cheese` + +`dii 1 ei/3` + +Deletes 1st item, 2nd item and 3rd item in the results of the `find-item` command. + +==== Deleting an item by name: `delete-item-name` or `din` + +Deletes the specified item from the menu. + +Format: `delete-item-name ITEM_NAME` + +**** +* Deletes the item by the specified `ITEM_NAME` +* The `ITEM_NAME` *must be in the menu* +* `ITEM_NAME` is case-insensitive. e.g `burger` will match `Burger` **** Examples: -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +* `delete-item-name Apple Juice` + +Deletes the `Apple Juice` item from the menu. +* `din Cheese Fries` + +Deletes the `Cheese Fries` item from the menu. +//end::menuPart1[] + +==== Listing all items in the menu : `list-items` or `li` + +Shows a list of all items in the menu. + +Format: `list-items` -=== Selecting a person : `select` +==== Clearing all entries : `clear-menu` or `cm` -Selects the person identified by the index number used in the displayed person list. + -Format: `select INDEX` +Clears all entries from the menu. + +Format: `clear-menu` + +==== Selecting an item : `select-item` or `si` + +Selects the item identified by the index number used in the menu. + +Format: `select-item INDEX` **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. +* Selects the item and loads the page the item at the specified `INDEX` +* The index refers to the index number shown in the displayed item list * The index *must be a positive integer* `1, 2, 3, ...` **** Examples: -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +* `list-items` + +`select-item 2` + +Selects the 2nd item in the menu. +* `fi Burger` + +`si 1` + +Selects the 1st item in the results of the `find-item` command. -=== Listing entered commands : `history` +==== Locating items by name: `find-item` or `fi` -Lists all the commands that you have entered in reverse chronological order. + -Format: `history` +Finds items whose names contain any of the given keywords. + +Format: `find-item KEYWORD [MORE_KEYWORDS]...` + +**** +* The search is case insensitive. e.g `burger` will match `Burger` +* The order of the keywords does not matter. e.g. `Cheese Burger` will match `Burger Cheese` +* Only the name is searched +* Only full words will be matched e.g. `Bur` will not match `Burger` +* Items matching at least one keyword will be returned (i.e. `OR` search). e.g. `Cheese Burger` will return +`Cheese Burger`, `Cheese Fries` and `Beef Burger` +**** + +Examples: + +* `find-item Burger` + +Returns `burger` and `Cheese Burger` +* `fi Cheese Chocolate Fruit` + +Returns any item having names `Cheese`, `Chocolate`, or `Fruit` + +//tag::menuPart2[] +==== Filter menu by tag: `filter-menu` or `fm` + +Finds items whose tags match any of the given keywords. + +Format: `filter-menu KEYWORD [MORE_KEYWORDS]...` + +**** +* The search is case insensitive. e.g `burger` will match `Burger` +* Only filter by tag +* Only full words will be matched e.g. `Bur` will not match `Burger` +* Items with tags matching at least one keyword will be returned (i.e. `OR` search). e.g. `drinks burger` will return +items that are tagged with drinks or burger +**** + +[NOTE] +`KEYWORD` for `filter-menu` must be alphanumeric + +Examples: + +* `filter-menu monday` + +Returns any item that contains tag `monday` +* `fm set monday` + +Returns any item that contains tag `set` or `monday` +//end::menuPart2[] + +==== Sort menu : `sort-menu` or `sm` + +Sort the menu by name or price. + +Format: `sort-menu SORTING_METHOD` + +**** +* Sort the menu by name or price +* `SORTING_METHOD` refers the method to sort menu: name or price +* Case-insensitive, it can be: `sort-menu name` or `sort-menu NAME` +* Only one of the sorting method should be provided +**** + +Examples: + +* `sort-menu name` + +Sorts the menu by item name in lexicographical order. +* `sm PRICE` + +Sorts the menu by item price in ascending order(lowest to highest). + +//tag::menuPart3[] +==== Giving a discount to an item : `discount-item` or `dci` + +Gives the item identified by the index number used in the displayed item list a discount. + +Format: `discount-item INDEX|ALL [ei/INDEX] dp/PERCENTAGE` + +**** +* You can remove discount by typing `0` for the percentage +* You can give a discount to all items in the displayed item list by typing `ALL`(case-insensitive) instead of a +specified `INDEX` +* You can give a discount to a range of items by adding the ending index `ei\INDEX` +* The ending index can not be smaller than starting index +* The percentage should only contain numbers(no negative), and it should be at most 2 digits(no decimal place) +**** + +Examples: + +* `list-items` + +`discount-item 2 dp/20` + +Give the 2nd item in the menu a 20% discount. +* `fi Cheese` + +`dci ALL dp/0` + +Revert all items in the results of the `find-item` command to original price. +* `li` + +`dci 1 ei/3 dp/50` + +Give the 1st item to the 3rd item in the menu a 50% discount. +//end::menuPart3[] + +==== Today's specials : `today-special` or `ts` + +Lists the items that have been tagged with `DAY_OF_THE_WEEK` in the menu. + +Format: `today-special` + +**** +* `DAY_OF_THE_WEEK` refers to Monday, Tuesday, ..., Sunday +**** +Examples: + +* `today-special` + +If today is Monday + +List the items that have been tagged with `Monday` in the menu. + +==== Adding recipe to item : `recipe-item` or `ri` + +Adds recipe to the item identified by the index number used in the menu. + +Format: `recipe-item INDEX r/RECIPE` + +**** +* Adds a recipe to the item at the specified `INDEX` +* If the item has a recipe already, it will overwite the existing recipe +* The index refers to the index number shown in the displayed item list +* The index *must be a positive integer* `1, 2, 3, ...` +* You can remove recipe by typing `r/` without specifying any recipe after it +**** + +Examples: + +* `recipe-item 1 r/Some Recipe` + +Add/Update the recipe of the 1st item to be `Some Recipe`. +* `fi Cheese` + +`ri 1 r/Other Recipe` + +Add/Update the recipe of the 1st item in the results of the `find-item` command to be `Other Recipe`. + +[[add-required-ingredient]] +==== Adding required ingredients: `add-required-ingredients` or `ari` + +Adds required ingredients to the item identified by the index number used in the menu. + +Format: `add-required-ingredients INDEX n/INGREDIENT_NAME... nu/NUMBER_OF_INGREDIENTS...` + +**** +* Adds required ingredients to the item at the specified `INDEX`. +* If the item has required ingredients already, it will overwrite the existing required ingredients. +* The index refers to the index number shown in the displayed item list. +* The index *must be a positive integer* `1, 2, 3, ...` +* NUMBER_OF_INGREDIENTS for an ingredient must be paired with the INGREDIENT_NAME for that particular ingredient. +e.g. `n/apple nu/3` or `nu/3 n/apple` +* You can remove required ingredients by not including any `n/INGREDIENT_NAME... nu/NUMBER_OF_INGREDIENTS...` +e.g. `add-required-ingredients INDEX` +**** + +Examples: + +* `add-required-ingredients 1 n/Apple nu/3` + +Add/Update the required ingredients of the 1st item. +* `fi Cheese` + +`ari 1 n/chicken thigh u/10 n/potato u/20` + +Add/Update the recipe of the 1st item in the results of the `find-item` command. + +//tag::reservations[] +=== Reservations Management +==== Adding a reservation: `add-reservation` or `ar` + +Adds a new reservation to the reservations list. + +Format: `add-reservation n/NAME px/PAX d/DATE ti/TIME` or `ar n/NAME px/PAX d/DATE ti/TIME` [NOTE] ==== -Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. +* We use natural language processing to parse the date and time values +* For example, phrases like `21st Dec` or `12 p.m.` will be accepted. Try it out! ==== -// tag::undoredo[] -=== Undoing previous command : `undo` +[WARNING] +==== +* Try to avoid ambiguous language as it might be interpreted incorrectly +* For example, `3/12` is ambiguous as it could mean `3rd December` or `12th March`! +* If you want to be sure, we recommend using these formats: +** `DATE`: `DD-MM-YYYY` +** `TIME`: `HH:MM` +* Dates that have already passed will be rejected +==== + +Examples: + +* `add-reservation n/TAN px/4 d/21-07-2019 ti/10:00` +* `ar n/ONG px/2 d/21st July ti/10am` +* `ar n/LEE px/8 d/next tuesday ti/8 p.m.` + +==== Listing all reservations : `list-reservations` or `lr` + +Shows a list of all reservations in the reservations list. + +Format: `list-reservations` or `lr` + +==== Editing a reservation: `edit-reservation` or `er` + +Edits an reservation in the reservation list. + +Format: `edit-reservation INDEX [n/NAME] [px/PAX] [d/DATE] [ti/TIME]` or `er INDEX [n/NAME] [px/PAX] [d/DATE] [ti/TIME]` + +**** +* Edits the reservation at the specified `INDEX`. The index refers to the index number shown in the +displayed reservations 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. +**** -Restores the address book to the state before the previous _undoable_ command was executed. + -Format: `undo` +Examples: + +* `edit-reservation 2 d/31-12-2019 ti/18:00` + +Edits the time of the 2nd reservation in the list to `31st Dec 2019, 1800` hrs. + +* `er 6 n/ONG px/4` + +Edits the name and pax of the 6th reservation to `ONG` and `4` respectively. + +==== Sorting reservations list: `sort-reservations` or `sortr` + +Sorts the reservations list by Date/Time. + +Format: `sort-reservations` or `sortr` + +==== Deleting a reservation: `delete-reservation` or `dr` + +Deletes the specified reservation from the reservations list. + +Format: `delete-reservation INDEX` or `dr INDEX` + +**** +* Deletes the reservation at the specified `INDEX`. +* The index refers to the index number shown in the displayed reservations list. +* The index *must be a positive integer* 1, 2, 3, ... +**** + +Examples: + +* `list-reservations` + +`delete-reservation 2` + +`list-reservations` + +Deletes the 2nd reservation in the reservations list. + +* `list-reservations` + +`dr 5` + +`list-reservations` + +Deletes the 5th reservation in the reservations list. + +==== Selecting a reservation : `select-reservation` or `sr` + +Selects the reservation identified by the index number used in the displayed reservations list. + +Format: `select-reservation INDEX` or `sr INDEX` + +**** +* Selects the reservation at the specified `INDEX`. +* The index refers to the index number shown in the displayed reservation list. +* The index *must be a positive integer* `1, 2, 3, ...` +**** + +Examples: + +* `list-reservations` + +`select-reservation 2` + +Selects the 2nd reservation in the reservations list. + +* `list-reservations` + +`select-reservation 7` + +Selects the 7th reservation in the reservations list. +//end::reservations[] + +// tag::salesmanagement[] +=== Sales Management + +The following are sales-related commands to help you with financial tracking and analysis. The terms **"sales record"** +and **"record list"** will be used frequently under this section so it is advisable to first understand their +definition in the <>. + +[[record-sales]] +==== Recording sales volume of an item: `record-sales` or `rs` + +Records the sales volume of an item within a specific day into the record list. + +Format: `record-sales d/DATE n/ITEM_NAME q/QUANTITY_SOLD p/ITEM_PRICE` or `rs d/DATE n/ITEM_NAME q/QUANTITY_SOLD +p/ITEM_PRICE` + + +**** +* `DATE` must be written in the DD-MM-YYYY format. +* `DATE` must exist in the calendar. +* Both `DATE` and `ITEM_NAME` cannot be same as another record in the record list. +* `ITEM_NAME` is case insensitive. "Cheesy Pasta" and "cheesy pasta" are regarded as the same item. +**** + +Examples: + +* `record-sales d/25-09-2018 n/Fried Rice q/35 p/5.50` +* `rs d/25-09-2018 n/Fried Rice q/35 p/5.50` + +[[auto-ingredient-update]] +**Auto-ingredient update mechanism** + +`record-sales` also determines all the ingredients you used and automatically deducts them from the ingredient +list, subjected to the following conditions: + +1) The item exists in the menu. + +See <> for more information. + +2) The required ingredients to make one unit of `ITEM_NAME` is specified. + +See <> for more information. + +3) All required ingredients exist in the ingredient list. + +See <> for more information. + +4) There are sufficient ingredients to make `QUANTITY_SOLD` units of `ITEM_NAME`. + +See <> for more information. [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Sales volume will still be recorded even if some of the above conditions are not met. However, ingredient list +will not be updated +==== + +[NOTE] +==== +[[ingredients-used]]**Ingredient used**: MeNUS will remember the name and quantity of ingredients you used as long as +the item and its required ingredients have been specified in the menu component +==== + +[TIP] ==== +If you recorded a sales volume by mistake, you can use the `Undo` command to recover the deducted ingredients, +provided that the ingredients have been deducted automatically +==== + +[[display-sales]] +==== Display sales report: `display-sales` or `dis` + +Generate and display the sales report for a specific day. + +Format: `display-sales DATE` or `dis DATE` + +**** +* `DATE` must be written in the DD-MM-YYYY format. +* `DATE` must exist in the calendar. +* There must be at least one sales record associated with the specified `DATE`. +**** Examples: -* `delete 1` + -`list` + -`undo` (reverses the `delete 1` command) + +* `display-sales 30-09-2018` +* `dis 30-09-2018` + +Displays the sales report dated 30-09-2018. -* `select 1` + -`list` + -`undo` + -The `undo` command fails as there are no undoable commands executed previously. +The following (Figure 5.6.2.1) is an example of what you will see if the sales report is generated and displayed +correctly: -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +image::display-sales-pic.png[width="465"] +_Figure 5.6.2.1: Sales Report_ -=== Redoing the previously undone command : `redo` +==== Deleting a sales record: `delete-sales` or `des` -Reverses the most recent `undo` command. + -Format: `redo` +Deletes the sales record identified by the index. + +Format: `delete-sales INDEX` or `des INDEX` + +**** +* Deletes the record at the specified `INDEX`. +* `INDEX` refers to the index number shown in the record list. +* `INDEX` *must be a positive integer* 1, 2, 3, ... +**** + +[NOTE] +==== +Deleting a sales record will not undo the effect of <> which may or + may not have happened during recording +==== Examples: -* `delete 1` + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +* `delete-sales 2` +* `des 2` + +Deletes the 2nd record from the record list. -* `delete 1` + -`redo` + -The `redo` command fails as there are no `undo` commands executed previously. +==== Editing a sales record: `edit-sales` or `es` -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + -`redo` (reapplies the `clear` command) + -// end::undoredo[] +Edits the sales record identified by the index. + +Format: `edit-sales INDEX [d/DATE] [n/ITEM_NAME] [q/QUANTITY_SOLD] [p/ITEM_PRICE]` or `es INDEX [d/DATE] +[n/ITEM_NAME] [q/QUANTITY_SOLD] [p/ITEM_PRICE]` + +**** +* Edits the record at the specified `INDEX`. +* `INDEX` refers to the index number shown in the record list. +* `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. +**** + +[WARNING] +==== +Editing a sales record will permanently delete its <> data +==== + +[NOTE] +==== +Editing a sales record will **not** update the ingredient list. See <> for more information +==== -=== Clearing all entries : `clear` +Examples: -Clears all entries from the address book. + -Format: `clear` +* `edit-sales 3 n/Fried Omelet` +* `es 3 n/Fried Omelet` + +Edits the item name of the 3rd record to be "Fried Omelet". -=== Exiting the program : `exit` +* `edit-sales 7 q/37 p/6.50` +* `es 7 q/37 p/6.50` + +Edits the quantity sold and price of the 7th record to be 37 and 6.50 respectively. +// end::salesmanagement[] -Exits the program. + -Format: `exit` +[[chart-sales]] +==== Display sales chart of date and revenue: `chart-sales` or `cs` -=== Saving the data +Generates and displays the sales chart of revenue against date. + +Format: `chart-sales` or `cs` -Address book data are saved in the hard disk automatically after any command that changes the data. + -There is no need to save manually. +**** +* Record list must not be empty. +* Only existing dates in the record list are tabulated and shown. +**** -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +The following (Figure 5.6.5.1) is an example of what you will see if the sales chart is generated and displayed correctly: -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +image::chart-sales-pic.png[width="550"] +_Figure 5.6.5.1: Sales Chart of revenue against date_ -== FAQ +// tag::rankdates[] -*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 Address Book folder. +[[rank-date]] +==== Ranking dates according to total revenue: `rank-date` or `rad` -== Command Summary +Ranks and displays all existing dates in the record list based on the total revenue. + +Format: `rank-date` or `rad` + +**** +* Record list must not be empty. +**** + +[TIP] +==== +You can generate the sales report of a specific date to get a more detailed analysis. See <> for more information +==== + + +The following (Figure 5.6.6.1) is an example of what you will see if the ranking is generated and displayed correctly: + +image::rank-date-pic.png[width="325"] +_Figure 5.6.6.1: Ranking of dates by revenue_ + +// end::rankdates[] + +[[rank-item]] +==== Ranking items according to total revenue: `rank-item` or `rai` + +Ranks and displays all items according to their total revenue accumulated in past sales records. + +Format: `rank-item` or `rai` + +**** +* Record list must not be empty. +**** + +[TIP] +==== +You can check the sales history of a specific item to get a more detailed analysis. This feature will be available +in v2.0. See <> for more information +==== + +The following (Figure 5.6.7.1) is an example of what you will see if the ranking is generated and displayed correctly: + +image::rank-item-pic.png[width="325"] +_Figure 5.6.7.1: Ranking of items by revenue_ + +==== Selecting a sales record : `select-sales` or `ss` + +Selects the sales record identified by the index number used in the record list. + +Format: `select-sales INDEX` or `ss INDEX` + +**** +* Loads the record list and selects the record at the specified `INDEX`. +* `INDEX` refers to the index number shown in the displayed record list. +* `INDEX` *must be a positive integer* `1, 2, 3, ...` +**** + +Examples: + +`select-sales 2` + +Selects the 2nd record in the record list. + +`ss 6` + +Selects the 6th record in the record list. + +=== Upcoming Features +[[Register-Improvement]] +==== Improving registration `[coming in v2.0]` +Improves the existing `register` command by including role (i.e. privilege system). + +Format: `register id/USERNAME pw/PASSWORD n/FULL_NAME r/ROLE_ID` or `reg id/USERNAME pw/PASSWORD n/FULL_NAME r/ROLE_ID` + +Examples: + +* `register id/azhikai pw/1122qq n/Ang Zhi Kai r/999` +* `reg id/azhikai pw/1122qq n/Ang Zhi Kai r/999` + +[[User-Role]] +==== Creating user role `[coming in v2.0]` +Creates a user role. User assigned with higher ranking role can execute more commands. + +[TIP] +==== +When the application is in its default state, this command can only be executed by the `root` account +which has the default role of `Administrator` +==== +Format: `create-role r/RANK n/ROLE_NAME` or `cr r/RANK n/ROLE_NAME` + +Examples: + +* `create-role r/999 n/Owner` +* `cr id/999 n/Owner` +* `create-role r/2 n/Supervisor` +* `create-role r/1 n/Employee` + +==== Editing user role `[coming in v2.0]` +Edits an existing user role. + +Format: `edit-role r/RANK [nr/NEW_RANK] [n/ROLE_NAME]` or `er r/RANK [nr/NEW_RANK] [n/ROLE_NAME]` + +Examples: + +* `edit-role r/999 n/Administrator` +* `er id/999 n/Administrator` +* `edit-role r/2 nr/3 n/Manager` + +==== Deleting user role `[coming in v2.0]` +Deletes an existing user role. + +Format: `delete-role r/RANK` or `dr r/RANK` + +Examples: + +* `delete-role r/999` +* `dr id/999` + +// tag::salesv20[] +==== Auto-compute sales profit `[coming in v2.0]` +Automatically determines the cost incurred in each sales record and computes its profit. The concept of profit +will be extended to the following features: + + 1) <> - Include a profit column in the sales report + + 2) <> - Display sales chart of date and profit + + 3) <> - Rank dates by profit + + 4) <> - Rank items by profit + +[NOTE] +==== +A negative profit denotes a loss +==== -* *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` -* *Select* : `select INDEX` + -e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +[[check-history]] +==== Checking item sales history `[coming in v2.0]` +Generates the item sales report that consists of all past records associated with that item. + +Format: `check-history n/ITEM_NAME` or `ch n/ITEM_NAME` + +Examples: + +* `check-history n/Fried Rice` +* `ch n/Fried Rice` + +==== Exporting sales report `[coming in v2.0]` +Exports the sales report of a specific date into a pdf file. The generated .pdf sales report will be more +detailed than that displayed in app. + +Format: `export-sales DATE` or `exs DATE` + +==== Exporting sales chart `[coming in v2.0]` +Exports the sales chart into a pdf file. + +Format: `export-chart` or `exc` +// end::salesv20[] + +==== Checking item sales history `[coming in v2.0]` +Generates the item sales report that consists of all records associated with that item. + +Format: `check-history n/ITEM_NAME` or `ch n/ITEM_NAME` + +* `check-history n/Fried Rice` +* `ch n/Fried Rice` + +==== Showing history of past stock ups and consumption `[coming in v2.0]` + +Shows the history of past ingredient stock ups or consumption. + +Format: `view-history [STOCKUP] [CONSUMPTION]` or `vh [STOCKUP] [CONSUMPTION]` + +Examples: + +* `view-history stockup` +* `view-history consumption` +* `vh stockup consumption` + +// tag::menuv2[] +==== Exporting menu `[coming in v2.0]` + +Exports the menu to a particular file path. + +Format: `export-menu fp/FILEPATH fn/FILENAME` + +Examples: + +* `export-menu fp/test/ fn/menu.txt` + +Export the menu to test folder with the file name `menu.txt` + +==== Importing menu `[coming in v2.0]` + +Imports an existing menu from specified file path to replace current menu. + +Format: `import-menu fp/FILEPATH fn/FILENAME` + +Examples: + +* `import-menu fp/test/ fn/menu.txt` + +Import the menu.txt from test folder to replace current menu. + +==== Support for multiple menus `[coming in v2.0]` +User will be able to manage multiple menus without the need of exporting and importing for different version of the +current menu. +// end::menuv2[] + +== Frequently Asked Questions (FAQ) + +*Q: How do I transfer my data to another Computer?* + +*A*: Install the application in the other computer and overwrite the empty data file it creates with the file that +contains the data of your previous *MeNUS* folder. + +*Q: Where do I find the latest release of the application?* + +*A*: You can find all releases link:https://github.com/CS2103-AY1819S1-F10-4/main/releases[here]. + +== Command Summary +[width="100%",cols="10%,<45%,<45%",options="header",] +|======================================================================= +|COMMAND |FORMAT |EXAMPLE +|*Help* |`help` |`help` +|*Clear* |`clear` |`clear` +|*History* |`history` |`history` +|*Undo* |`undo` |`undo` +|*Redo* |`redo` |`redo` +|*Login* |`login id/USERNAME pw/PASSWORD` |`login id/root pw/1122qq` +|*Logout* |`logout` |`logout` +|*Create account* |`register id/USERNAME pw/PASSWORD n/FULL_NAME` |`register id/azhikai pw/1122qq n/Ang Zhi Kai` +|*Change password* |`change-password npw/NEW_PASSWORD` |`change-password npw/newp@55w0rd` +|*Delete account* |`deregister id/USERNAME` |`deregister id/azhikai` +|*Find account* |`find-account KEYWORD` |`find-account root` +|*Select account* |`select-account INDEX` |`select-account 1` +|*List accounts* |`list-accounts` |`list-accounts` +|*Add ingredient* |`add-ingredient n/INGREDIENT_NAME u/UNIT_TYPE p/PRICE_PER_UNIT m/MINIMUM` |`add-ingredient n/cod +fish u/kilogram p/20 m/1` +|*List ingredients* |`list-ingredients` |`list-ingredients` +|*List ingredients with low stock count* |`low-stock` |`low-stock` +|*Edit ingredient* |`edit-ingredient INDEX [n/INGREDIENT_NAME] [u/UNIT_TYPE] [p/PRICE_PER_UNIT] [m/MINIMUM]` or +`edit-ingredient on/ORIGINAL_INGREDIENT_NAME [n/NEW_INGREDIENT_NAME] [u/UNIT_TYPE] [p/PRICE_PER_UNIT] [m/MINIMUM]` +|`edit-ingredient 3 n/thin fries` or `edit-ingredient on/ketchup n/tomato ketchup` +|*Delete ingredient* |`delete-ingredient INDEX` or `delete-ingredient INGREDIENT_NAME` |`delete-ingredient 1` or +`delete-ingredient cod fish` +|*Stock up* |`stockup n/INGREDIENT_NAME... nu/NUMBER_OF_UNITS...` |`stockup n/cod fish nu/5` +|*Select ingredient* |`select-ingredient INDEX` |`select-ingredient 3` +|*Add item* |`add-item n/ITEM_NAME p/ITEM_PRICE [t/TAG]...` |`add-item n/Burger Set p/3 t/set` +|*List items* |`list-items` |`list-items` +|*Edit item* |`edit-item INDEX [n/ITEM_NAME] [p/ITEM_PRICE] [t/TAG]...` |`edit-item 2 n/Fries p/3` +|*Delete item by index* |`delete-item-index INDEX` |`delete-item-index 3` +|*Delete item by name* |`delete-item-name ITEM_NAME` |`delete-item-name burger` +|*Select item* |`select-item INDEX` |`select-item 3` +|*Find item* |`find-item KEYWORD [MORE_KEYWORDS]...` |`find-item Cheese Burger` +|*List items* |`list-items` |`list-items` +|*Clear menu* |`clear-menu` |`clear-menu` +|*Filter menu* |`filter-menu KEYWORD [MORE_KEYWORDS]...` |`filter-menu cheese` +|*Sort menu* |`sort-menu SORTING_METHOD` |`sort-menu NAME` +|*Discount item* |`discount-item INDEX/ALL [ei/INDEX] dp/PERCENTAGE` |`discount-item 2 dp/20` +|*Today's special* |`today-special` |`today-special` +|*Add recipe to item* |`recipe-item r/RECIPE` |`recipe-item r/some recipe` +|*Add required ingredients to item* |`add-required-ingredients n/INGREDIENT_NAME nu/NUMBER_OF_INGREDIENTS` +|`add-required-ingredients n/Apple nu/3` +|*Add reservation* |`add-reservation n/NAME px/PAX d/DATE ti/TIME` |`add-reservation n/TAN px/4 d/21-07-2019 ti/10:00` +|*Edit reservation* |`edit-reservation INDEX [n/NAME] [px/PAX] [d/DATE] [ti/TIME]` |`edit-reservation 2 px/2` +|*Delete reservation* |`delete-reservation INDEX` |`delete-reservation 2` +|*Select reservation* |`select-reservation INDEX` |`select-reservation 1` +|*List reservation* |`list-reservations` |`list-reservations` +|*Sort reservation* |`sort-reservations` |`sort-reservations` +|*Record sales volume* |`record-sales d/DATE n/ITEM_NAME q/QUANTITY_SOLD p/ITEM_PRICE` |`record-sales d/25-09-2018 n/Fried Rice q/35 p/5.50` +|*Display sales report* |`display-sales DATE` |`display-sales 25-09-2018` +|*Edit sales record* |`edit-sales INDEX [d/DATE] [n/ITEM_NAME] [q/QUANTITY_SOLD] [p/ITEM_PRICE]` |`edit-sales 3 n/Fried Omelet p/7.99` +|*Delete sales record* |`delete-sales INDEX` |`delete-sales 2` +|*Display sales chart* |`chart-sales` |`chart-sales` +|*Rank items by total revenue* |`rank-item` |`rank-item` +|*Rank dates by total revenue* |`rank-date` | `rank-date` +|*Select sales record* |`select-sales INDEX` |`select-sales 3` +|======================================================================= + +[appendix] +== Acronyms + +[[gui]] GUI:: +*Graphical User Interface* allows the use of icons or other visual indicators to interact with electronic devices, +rather than using only text via the command line. + +// tag::salesglossary[] +[[sales-record]] Sales record:: +A *sales record* is created whenever you <>. It is represented by a +single entry in the record list. It comprises 6 different attributes, namely + +1) `Date` of which the item was sold + +2) `Name` of the item + +3) `Quantity` of the item sold + +4) `Price` per item + +5) `Total revenue` earned + +6) `Ingredient used` to make the amount of item as specified by the quantity sold. + + +[[record-list]] Record list:: +The *record list* contains all the sales records you have recorded till date. It is shown under the +`List of data` section of the sales component in MeNUS's <>. +// end::salesglossary[] diff --git a/docs/UsingGit.adoc b/docs/UsingGit.adoc new file mode 100644 index 000000000000..81ee192dce74 --- /dev/null +++ b/docs/UsingGit.adoc @@ -0,0 +1,35 @@ += Using Git with Sourcetree +:site-section: DeveloperGuide +:imagesDir: images +:stylesDir: stylesheets +:experimental: +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +endif::[] + +== About Git and Sourcetree +https://git-scm.com/[Git] is a popular distributed version control software (VCS). It is used to track changes in +computer files and coordinating work on those files among multiple people. It is primarily used for source code +management in software development,but it can be used to keep track of changes in any set of files. + +https://www.sourcetreeapp.com/[Sourcetree] on the other hand is a *free* Git client for Windows and Mac. The key benefit +to Sourcetree is the visualization of the Git process. It can feel daunting when you're using the terminal, as +you have a limited view of what's happening behind the scenes. Managing large projects with multiple branches, +commits and developers can get messy. SourceTree can make the whole process more manageable. + +== Setting up +Follow this setup guide to get *Git with Sourcetree* up and running on your computer. + +. Download Git https://git-scm.com/downloads[here]. +. Download Sourcetree https://www.sourcetreeapp.com/[here] and login using your Atlassian or Bitbucket server account. +. Fork the repository you wish to contribute to on GitHub. +. Go to the forked repository and click on `Clone or download` and copy the link. +. Open Sourcetree and click `Clone` and paste the link into `Source Path / URL` and click `Clone`. +. You are now ready for some development work! + +[TIP] +==== +It is always a good idea to ensure that your forked repository is `ahead` or `even` with the upstream repository +==== + diff --git a/docs/UsingGradle.adoc b/docs/UsingGradle.adoc index d1be2f3b7c3a..29b28357fad3 100644 --- a/docs/UsingGradle.adoc +++ b/docs/UsingGradle.adoc @@ -83,9 +83,9 @@ The set of code style rules implemented can be found in `config/checkstyle/check * **`allTests`** + Runs all tests. * **`guiTests`** + -Runs all tests in the `seedu.address.ui` and `systemtests` package +Runs all tests in the `seedu.restaurant.ui` and `systemtests` package * **`nonGuiTests`** + -Runs all non-GUI tests in the `seedu.address` +Runs all non-GUI tests in the `seedu.restaurant` package * **`headless`** + Sets the test mode as _headless_. The mode is effective for that Gradle run only so it should be combined with other test tasks. diff --git a/docs/diagrams/AddReservationActivityDiagram.pptx b/docs/diagrams/AddReservationActivityDiagram.pptx new file mode 100644 index 000000000000..d512eff05bd9 Binary files /dev/null and b/docs/diagrams/AddReservationActivityDiagram.pptx differ diff --git a/docs/diagrams/DiscountItemActivityDiagram.pptx b/docs/diagrams/DiscountItemActivityDiagram.pptx new file mode 100644 index 000000000000..3669509ddf01 Binary files /dev/null and b/docs/diagrams/DiscountItemActivityDiagram.pptx differ diff --git a/docs/diagrams/DisplaySalesSequenceDiagram.pptx b/docs/diagrams/DisplaySalesSequenceDiagram.pptx new file mode 100644 index 000000000000..acc465f0d71b Binary files /dev/null and b/docs/diagrams/DisplaySalesSequenceDiagram.pptx differ diff --git a/docs/diagrams/DisplaySalesSequenceDiagramRef.pptx b/docs/diagrams/DisplaySalesSequenceDiagramRef.pptx new file mode 100644 index 000000000000..fd9894892f6e Binary files /dev/null and b/docs/diagrams/DisplaySalesSequenceDiagramRef.pptx differ diff --git a/docs/diagrams/HighLevelSequenceDiagrams.pptx b/docs/diagrams/HighLevelSequenceDiagrams.pptx index 38332090a79a..7f00cd14778b 100644 Binary files a/docs/diagrams/HighLevelSequenceDiagrams.pptx and b/docs/diagrams/HighLevelSequenceDiagrams.pptx differ diff --git a/docs/diagrams/ListReservationsSequenceDiagram.pptx b/docs/diagrams/ListReservationsSequenceDiagram.pptx new file mode 100644 index 000000000000..5eaa2525a6e7 Binary files /dev/null and b/docs/diagrams/ListReservationsSequenceDiagram.pptx differ diff --git a/docs/diagrams/LogicComponentClassDiagram.pptx b/docs/diagrams/LogicComponentClassDiagram.pptx index 6fcc1136a5bb..dd52a35f68b4 100644 Binary files a/docs/diagrams/LogicComponentClassDiagram.pptx and b/docs/diagrams/LogicComponentClassDiagram.pptx differ diff --git a/docs/diagrams/LogicComponentSequenceDiagram.pptx b/docs/diagrams/LogicComponentSequenceDiagram.pptx index c5b6d5fad6e3..6cbc0b070d0b 100644 Binary files a/docs/diagrams/LogicComponentSequenceDiagram.pptx and b/docs/diagrams/LogicComponentSequenceDiagram.pptx differ diff --git a/docs/diagrams/LoginSequenceDiagram.pptx b/docs/diagrams/LoginSequenceDiagram.pptx new file mode 100644 index 000000000000..cce00387bd74 Binary files /dev/null and b/docs/diagrams/LoginSequenceDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx b/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx index d0561dfd305a..bc9d4a3b0a5a 100644 Binary files a/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx and b/docs/diagrams/ModelComponentClassBetterOopDiagram.pptx differ diff --git a/docs/diagrams/PasswordActivityDiagram.pptx b/docs/diagrams/PasswordActivityDiagram.pptx new file mode 100644 index 000000000000..a2a23896c529 Binary files /dev/null and b/docs/diagrams/PasswordActivityDiagram.pptx differ diff --git a/docs/diagrams/PasswordHashActivityDiagram.pptx b/docs/diagrams/PasswordHashActivityDiagram.pptx new file mode 100644 index 000000000000..a5752f06a5cc Binary files /dev/null and b/docs/diagrams/PasswordHashActivityDiagram.pptx differ diff --git a/docs/diagrams/RecordSalesActivityDiagram.pptx b/docs/diagrams/RecordSalesActivityDiagram.pptx new file mode 100644 index 000000000000..b3b780e8f622 Binary files /dev/null and b/docs/diagrams/RecordSalesActivityDiagram.pptx differ diff --git a/docs/diagrams/RegisterSequenceDiagram.pptx b/docs/diagrams/RegisterSequenceDiagram.pptx new file mode 100644 index 000000000000..eb8cd9d3c8ab Binary files /dev/null and b/docs/diagrams/RegisterSequenceDiagram.pptx differ diff --git a/docs/diagrams/SortMenuActivityDiagram.pptx b/docs/diagrams/SortMenuActivityDiagram.pptx new file mode 100644 index 000000000000..53c8b3a913a0 Binary files /dev/null and b/docs/diagrams/SortMenuActivityDiagram.pptx differ diff --git a/docs/diagrams/SortMenuSequenceDiagram.pptx b/docs/diagrams/SortMenuSequenceDiagram.pptx new file mode 100644 index 000000000000..51773811729a Binary files /dev/null and b/docs/diagrams/SortMenuSequenceDiagram.pptx differ diff --git a/docs/diagrams/StockUpActivityDiagram.png b/docs/diagrams/StockUpActivityDiagram.png new file mode 100644 index 000000000000..fc1f7d934ff6 Binary files /dev/null and b/docs/diagrams/StockUpActivityDiagram.png differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..e90d7bf98c41 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UiComponentClassDiagram.pptx b/docs/diagrams/UiComponentClassDiagram.pptx index 384d0a00e6ea..5e4110543070 100644 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoActivityDiagram.pptx b/docs/diagrams/UndoRedoActivityDiagram.pptx index 16fec930cf3f..0a14e4901e92 100644 Binary files a/docs/diagrams/UndoRedoActivityDiagram.pptx and b/docs/diagrams/UndoRedoActivityDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx b/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx index 6fd31b5f3fbd..c6c9c900b909 100644 Binary files a/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx and b/docs/diagrams/UndoRedoExecuteUndoStateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx index 1f3261976dce..6eea22c5c336 100644 Binary files a/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand1StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx index e2907d4a9cae..fac57e265399 100644 Binary files a/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand2StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx index 4ecc659bd600..b2fa786be497 100644 Binary files a/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand3StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx b/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx index 16ebf585ddbd..8718426588c6 100644 Binary files a/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand4StateListDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoSequenceDiagram.pptx b/docs/diagrams/UndoRedoSequenceDiagram.pptx index 5ccc1042caac..97627f912019 100644 Binary files a/docs/diagrams/UndoRedoSequenceDiagram.pptx and b/docs/diagrams/UndoRedoSequenceDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoStartingStateListDiagram.pptx b/docs/diagrams/UndoRedoStartingStateListDiagram.pptx index 98ce067642ff..61419b7c6c80 100644 Binary files a/docs/diagrams/UndoRedoStartingStateListDiagram.pptx and b/docs/diagrams/UndoRedoStartingStateListDiagram.pptx differ diff --git a/docs/diagrams/UserInterfaceHighlight.pptx b/docs/diagrams/UserInterfaceHighlight.pptx new file mode 100644 index 000000000000..c2f2b7664cbe Binary files /dev/null and b/docs/diagrams/UserInterfaceHighlight.pptx differ diff --git a/docs/diagrams/UserSessionActivityDiagram.pptx b/docs/diagrams/UserSessionActivityDiagram.pptx new file mode 100644 index 000000000000..b55b98c0b56d Binary files /dev/null and b/docs/diagrams/UserSessionActivityDiagram.pptx differ diff --git a/docs/images/AddReservationActivityDiagram.png b/docs/images/AddReservationActivityDiagram.png new file mode 100644 index 000000000000..b131e1b58a7b Binary files /dev/null and b/docs/images/AddReservationActivityDiagram.png differ diff --git a/docs/images/DeleteIngredientByIndexSequenceDiagram.png b/docs/images/DeleteIngredientByIndexSequenceDiagram.png new file mode 100644 index 000000000000..7b58170e1867 Binary files /dev/null and b/docs/images/DeleteIngredientByIndexSequenceDiagram.png differ diff --git a/docs/images/DeleteIngredientByNameSequenceDiagram.png b/docs/images/DeleteIngredientByNameSequenceDiagram.png new file mode 100644 index 000000000000..558bc0432a48 Binary files /dev/null and b/docs/images/DeleteIngredientByNameSequenceDiagram.png differ diff --git a/docs/images/DiscountItemActivityDiagram.png b/docs/images/DiscountItemActivityDiagram.png new file mode 100644 index 000000000000..72da57a0a3c0 Binary files /dev/null and b/docs/images/DiscountItemActivityDiagram.png differ diff --git a/docs/images/DisplaySalesSequenceDiagram.png b/docs/images/DisplaySalesSequenceDiagram.png new file mode 100644 index 000000000000..25f60d433d3d Binary files /dev/null and b/docs/images/DisplaySalesSequenceDiagram.png differ diff --git a/docs/images/DisplaySalesSequenceDiagramRef.png b/docs/images/DisplaySalesSequenceDiagramRef.png new file mode 100644 index 000000000000..ea67d4444d74 Binary files /dev/null and b/docs/images/DisplaySalesSequenceDiagramRef.png differ diff --git a/docs/images/ListReservationsSequenceDiagram.png b/docs/images/ListReservationsSequenceDiagram.png new file mode 100644 index 000000000000..6e98b999bfee Binary files /dev/null and b/docs/images/ListReservationsSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index f4ecf65b3193..8660ae061651 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/LoginSequenceDiagram.png b/docs/images/LoginSequenceDiagram.png new file mode 100644 index 000000000000..d5d5cc44e805 Binary files /dev/null and b/docs/images/LoginSequenceDiagram.png differ diff --git a/docs/images/ModelClassBetterOopDiagram.png b/docs/images/ModelClassBetterOopDiagram.png index 9ba8eb5e31d0..a6c1fbe6cfa6 100644 Binary files a/docs/images/ModelClassBetterOopDiagram.png and b/docs/images/ModelClassBetterOopDiagram.png differ diff --git a/docs/images/PasswordHashActivityDiagram.png b/docs/images/PasswordHashActivityDiagram.png new file mode 100644 index 000000000000..a795c24d91c4 Binary files /dev/null and b/docs/images/PasswordHashActivityDiagram.png differ diff --git a/docs/images/PasswordLoginActivityDiagram.png b/docs/images/PasswordLoginActivityDiagram.png new file mode 100644 index 000000000000..9e3b4f925dc7 Binary files /dev/null and b/docs/images/PasswordLoginActivityDiagram.png differ diff --git a/docs/images/RecordSalesActivityDiagram.png b/docs/images/RecordSalesActivityDiagram.png new file mode 100644 index 000000000000..4638dc3770fa Binary files /dev/null and b/docs/images/RecordSalesActivityDiagram.png differ diff --git a/docs/images/RegisterAccountSdForLogic.png b/docs/images/RegisterAccountSdForLogic.png new file mode 100644 index 000000000000..710857c3e1d9 Binary files /dev/null and b/docs/images/RegisterAccountSdForLogic.png differ diff --git a/docs/images/RegisterSequenceDiagram.png b/docs/images/RegisterSequenceDiagram.png new file mode 100644 index 000000000000..96f61d5946a3 Binary files /dev/null and b/docs/images/RegisterSequenceDiagram.png differ diff --git a/docs/images/SDforDeleteItem.png b/docs/images/SDforDeleteItem.png new file mode 100644 index 000000000000..c7400e6aa1b0 Binary files /dev/null and b/docs/images/SDforDeleteItem.png differ diff --git a/docs/images/SDforDeleteItemEventHandling.png b/docs/images/SDforDeleteItemEventHandling.png new file mode 100644 index 000000000000..e4a370fe13a3 Binary files /dev/null and b/docs/images/SDforDeleteItemEventHandling.png differ diff --git a/docs/images/SDforDeletePerson.png b/docs/images/SDforDeletePerson.png deleted file mode 100644 index 1e836f10dcd8..000000000000 Binary files a/docs/images/SDforDeletePerson.png and /dev/null differ diff --git a/docs/images/SDforDeletePersonEventHandling.png b/docs/images/SDforDeletePersonEventHandling.png deleted file mode 100644 index ecec0805d32c..000000000000 Binary files a/docs/images/SDforDeletePersonEventHandling.png and /dev/null differ diff --git a/docs/images/SeEduLogo.png b/docs/images/SeEduLogo.png deleted file mode 100644 index 31ad50b6f88d..000000000000 Binary files a/docs/images/SeEduLogo.png and /dev/null differ diff --git a/docs/images/SortMenuActivityDiagram.png b/docs/images/SortMenuActivityDiagram.png new file mode 100644 index 000000000000..ba62a20dcd71 Binary files /dev/null and b/docs/images/SortMenuActivityDiagram.png differ diff --git a/docs/images/SortMenuSequenceDiagram.png b/docs/images/SortMenuSequenceDiagram.png new file mode 100644 index 000000000000..a3143aadf4f4 Binary files /dev/null and b/docs/images/SortMenuSequenceDiagram.png differ diff --git a/docs/images/StockUpActivityDiagram.png b/docs/images/StockUpActivityDiagram.png new file mode 100644 index 000000000000..fc1f7d934ff6 Binary files /dev/null and b/docs/images/StockUpActivityDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..ca6ea0ba73c5 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..76222fa76d25 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 369469ef176e..9f6e9b091c3f 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoRedoActivityDiagram.png b/docs/images/UndoRedoActivityDiagram.png index 55e4138cc64f..060a25f6330a 100644 Binary files a/docs/images/UndoRedoActivityDiagram.png and b/docs/images/UndoRedoActivityDiagram.png differ diff --git a/docs/images/UndoRedoExecuteUndoStateListDiagram.png b/docs/images/UndoRedoExecuteUndoStateListDiagram.png index 29c365d6b4a1..7f9eb731dbb3 100644 Binary files a/docs/images/UndoRedoExecuteUndoStateListDiagram.png and b/docs/images/UndoRedoExecuteUndoStateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand1StateListDiagram.png b/docs/images/UndoRedoNewCommand1StateListDiagram.png index 76e661d62027..6514c5369118 100644 Binary files a/docs/images/UndoRedoNewCommand1StateListDiagram.png and b/docs/images/UndoRedoNewCommand1StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand2StateListDiagram.png b/docs/images/UndoRedoNewCommand2StateListDiagram.png index adcb9aeadc51..77593c0e09f7 100644 Binary files a/docs/images/UndoRedoNewCommand2StateListDiagram.png and b/docs/images/UndoRedoNewCommand2StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand3StateListDiagram.png b/docs/images/UndoRedoNewCommand3StateListDiagram.png index aac9c5fe05db..9f0c1f136468 100644 Binary files a/docs/images/UndoRedoNewCommand3StateListDiagram.png and b/docs/images/UndoRedoNewCommand3StateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand4StateListDiagram.png b/docs/images/UndoRedoNewCommand4StateListDiagram.png index 66a0a3b5f323..75c843724996 100644 Binary files a/docs/images/UndoRedoNewCommand4StateListDiagram.png and b/docs/images/UndoRedoNewCommand4StateListDiagram.png differ diff --git a/docs/images/UndoRedoSequenceDiagram.png b/docs/images/UndoRedoSequenceDiagram.png index 5c9d5936f098..11d8c0444df7 100644 Binary files a/docs/images/UndoRedoSequenceDiagram.png and b/docs/images/UndoRedoSequenceDiagram.png differ diff --git a/docs/images/UndoRedoStartingStateListDiagram.png b/docs/images/UndoRedoStartingStateListDiagram.png index 002f3e2bbf79..f0d996bbdfca 100644 Binary files a/docs/images/UndoRedoStartingStateListDiagram.png and b/docs/images/UndoRedoStartingStateListDiagram.png differ diff --git a/docs/images/UserInterfaceHighlight.png b/docs/images/UserInterfaceHighlight.png new file mode 100644 index 000000000000..1d0eec0e427c Binary files /dev/null and b/docs/images/UserInterfaceHighlight.png differ diff --git a/docs/images/UserSessionActivityDiagram.png b/docs/images/UserSessionActivityDiagram.png new file mode 100644 index 000000000000..795364961b46 Binary files /dev/null and b/docs/images/UserSessionActivityDiagram.png differ diff --git a/docs/images/account.png b/docs/images/account.png new file mode 100644 index 000000000000..536b71a374d3 Binary files /dev/null and b/docs/images/account.png differ diff --git a/docs/images/chart-sales-pic.png b/docs/images/chart-sales-pic.png new file mode 100644 index 000000000000..46035c412332 Binary files /dev/null and b/docs/images/chart-sales-pic.png differ diff --git a/docs/images/damithc.jpg b/docs/images/damithc.jpg deleted file mode 100644 index 127543883893..000000000000 Binary files a/docs/images/damithc.jpg and /dev/null differ diff --git a/docs/images/display-sales-pic.png b/docs/images/display-sales-pic.png new file mode 100644 index 000000000000..09dcf55df7dc Binary files /dev/null and b/docs/images/display-sales-pic.png differ diff --git a/docs/images/ingredient.png b/docs/images/ingredient.png new file mode 100644 index 000000000000..f18b89d42c8d Binary files /dev/null and b/docs/images/ingredient.png differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/menu.png b/docs/images/menu.png new file mode 100644 index 000000000000..c7c61c3eef29 Binary files /dev/null and b/docs/images/menu.png differ diff --git a/docs/images/rank-date-pic.png b/docs/images/rank-date-pic.png new file mode 100644 index 000000000000..bdae3c6735b8 Binary files /dev/null and b/docs/images/rank-date-pic.png differ diff --git a/docs/images/rank-item-pic.png b/docs/images/rank-item-pic.png new file mode 100644 index 000000000000..b2888c216642 Binary files /dev/null and b/docs/images/rank-item-pic.png differ diff --git a/docs/images/reservation.png b/docs/images/reservation.png new file mode 100644 index 000000000000..1c75ecc93bc4 Binary files /dev/null and b/docs/images/reservation.png differ diff --git a/docs/images/sales.png b/docs/images/sales.png new file mode 100644 index 000000000000..8c72bcd32e22 Binary files /dev/null and b/docs/images/sales.png differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a732272..000000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/team/azhikai.adoc b/docs/team/azhikai.adoc new file mode 100644 index 000000000000..bb19190f123e --- /dev/null +++ b/docs/team/azhikai.adoc @@ -0,0 +1,100 @@ += Ang Zhi Kai - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: MeNUS +--- +This portfolio aims to provide a summary of my contribution as Team Lead for the project, +https://github.com/CS2103-AY1819S1-F10-4/main[*MeNUS*]. + +== Overview + +*MeNUS* is a restaurant management desktop application targeting restaurant owners in the National University +of Singapore (NUS). It is built to help them better manage core aspects of their restaurants  --  accounts (i.e. employees), menus, +ingredients, sales and reservations management -- all within a single application! + +Restaurant owners interact with *MeNUS* using a Command Line Interface +(CLI). *MeNUS* also has a Graphical User Interface(GUI) created with https://docs.oracle.com/javafx/2/[JavaFX] and it +is written using https://www.oracle.com/technetwork/java/javase/downloads/java-archive-javase9-3934878.html[Java 9] with +approximately 30 KLoC. + +It is cross-platform supported and can be used on both Windows-based and Unix-based platforms. + +== Summary of Contributions +* *Code contributed*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=azhikai[RepoSense] +* *Major enhancement*: Added the *accounts management* feature +** *What it does*: Allows the restaurant owner to create an account for each of his employee to manage the system on his + behalf. +** *Justification*: This feature implements a security layer by ensuring that only authorised employees are able to +execute commands based on their level of privilege. In addition, it provides accountability as executed commands +can be traced back to the respective user. +** *Highlights*: This enhancement affects existing commands as certain commands will only be executed if the user is +authorised to do so. It required an in-depth analysis and understanding of the `Logic`,`Model` and `Storage` +components. Also, the implementation was challenging as it required modifications to existing commands. +** *Credits*: Passwords are secured using https://github.com/patrickfav/bcrypt[`bcrypt`]. + +* *Other contributions*: +** *Minor enhancements*: +*** Added side navigation bar to allow access to the different components of the application with a click of a mouse +or trackpad, in addition to doing so via the CLI. +*** https://github.com/CS2103-AY1819S1-F10-4/main/pull/256[Morphed] the code base into a different product. +** *Project management*: +*** Team Lead; responsible for overall project coordination. +*** Set up and configured GitHub repository, Travis, AppVeyor, Codacy, Coveralls, Netlify, RepoSense and Discord +https://i.imgur.com/iHyQAML.png[automatic notifications] using Webhook. +*** Maintained issue tracker, milestone, project board and merging of pull requests (PRs). +*** Managed https://github.com/CS2103-AY1819S1-F10-4/main/releases[releases] `v1.1`, `v1.2`, `v1.2.1`, `v1.3`, +`v1.3.1`, `v1.4` (6 releases) on GitHub. +** *Documentation*: +*** Restructured https://cs2103-ay1819s1-f10-4.github.io/main/[project website] to resemble an actual product. +*** Added introductions to User Guide and Developer Guide to make them more reader-focused. +*** Added `Using Git with Sourcetree` section to the Developer Guide. +*** Added instructions for manual testing for Accounts Management. +*** Morphed the existing contents of the User Guide and Developer Guide with respect to +the morphed code base. +** *Community*: +*** http://bit.ly/azhikai-contribution[Contributed] more than 50 PRs. +*** http://bit.ly/azhikai-PR-review[Reviewed] more than 80 PRs, most with non-trivial review comments. +*** Contributed to forum discussions using GitHub's Issue Tracker: +https://github.com/nus-cs2103-AY1819S1/forum/issues/7[#7], +https://github.com/nus-cs2103-AY1819S1/forum/issues/20[#20], +https://github.com/nus-cs2103-AY1819S1/forum/issues/33[#33], +https://github.com/nus-cs2103-AY1819S1/forum/issues/79[#79] +*** Reported bugs and suggestions for other teams in the class: +https://github.com/nus-cs2103-AY1819S1/forum/issues/74#issuecomment-430956137[#74], +https://github.com/CS2103-AY1819S1-W14-2/main/issues/110[#110], +https://github.com/CS2103-AY1819S1-W14-2/main/issues/111[#111], +https://github.com/CS2103-AY1819S1-W14-2/main/issues/112[#112], +https://github.com/CS2103-AY1819S1-W14-2/main/issues/113[#113], +https://github.com/CS2103-AY1819S1-W14-2/main/issues/124[#124], +https://github.com/CS2103-AY1819S1-W14-2/main/issues/130[#130] +** *Tools*: +*** Integrated a third party library (bcrypt) to the project: +https://github.com/CS2103-AY1819S1-F10-4/main/pull/38[#38], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/70[#70] +** *Miscellaneous*: +*** Practiced good PR hygiene by ensuring no loss of code coverage. +*** Chaired weekly face-to-face meeting. + +== Contributions to the User Guide + +|=== +|_The following sections are my contribution to the User Guide. They showcase my ability to write documentation +targeting end-users with little or no technical knowledge._ +|=== + +include::../UserGuide.adoc[tag=intro] + +include::../UserGuide.adoc[tag=accountmanagement] + +== Contributions to the Developer Guide + +|=== +|_The following sections are my contribution to the Developer Guide. They showcase my ability to write +technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=intro] + +include::../DeveloperGuide.adoc[tag=useraccount] diff --git a/docs/team/hyperionnkj.adoc b/docs/team/hyperionnkj.adoc new file mode 100644 index 000000000000..104412d5d1da --- /dev/null +++ b/docs/team/hyperionnkj.adoc @@ -0,0 +1,139 @@ += Neo Kai Jun - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== Overview +This portfolio documents my contributions towards the project I have undertaken in the course **CS2103T - Software +Engineering**. It aims to demonstrate the theoretical and practical knowledge I have gained in the field of Software +Engineering over the semester. + +== PROJECT: MeNUS +*MeNUS* is an integrated restaurant management desktop application designed for restaurant owners. It comprises five +sub-management systems - 1) Menu, 2) Inventory, 3) Sales, 4) Reservation and 5) Authentication, each of which plays an +indispensable role in ensuring the success of a restaurant business. They were meticulously developed to streamline +the process of tracking dishes, ingredients, bookings and sales records while upholding a substantial level of security. + +*MeNUS* provides a Command Line Interface (CLI) for restaurant owners to interact with and is complemented with a +Graphical User Interface (GUI) that is constructed with https://docs.oracle.com/javafx/2/overview/jfxpub-overview.htm[JavaFX]. +MeNUS is written in https://www.oracle.com/technetwork/java/javase/downloads/java-archive-javase9-3934878.html[Java 9] +with approximately 25 kLOC. + + +== Summary of contributions + +|=== +|Given below are contributions I made for the project. They showcase my capability to + + 1) identify and specify functional and non-functional requirements for the product; + + 2) conduct product and implementation design to meet end-users' requirements; + + 3) write high quality code that is error-free, comprehensible, and reusable; + + 4) write effective and efficient test codes to ensure high quality assurance; + + 5) use various modelling and version control tools; + + 6) collaborate with other developers. +|=== + +* *Code contributed*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=hyperionnkj[RepoSense] +* *Major enhancement*: Developed and integrated a **Sales Management System (SMS)** into MeNUS to help restaurant owners +keep track of financial records, analyse their financial performance and devise marketing strategies. +** *What it does*: SMS allows owners to add, modify and remove sales records. An **Auto-ingredient update** mechanism +is incorporated to enable automatic consumption of ingredients whenever a record is added. Also provides owners +with analytical features to generate sales report, plot sales chart and rank dates/items according to +profitability. + +** *Justification*: These features provide efficiency and effectiveness in keeping track of huge amount of financial +records every day which can be extremely tedious if done via the conventional means of pen and paper. The +`Auto-ingredient update mechanism` also saves restaurant managers the trouble of frequent inventory tracking which +often takes up a significant amount of time and effort. Analytical features assists restaurant owners in making +strategic marketing decisions, projecting business's revenue as well as monitoring its long-term growth. These ensure + the financial success of the restaurant in the long run. + +** *Highlights*: This enhancement adds on to the limited number of key management aspects MeNUS can provide. It +requires an in-depth analysis of design alternatives as well as a thorough understanding of the entire code +architecture. The implementation was challenging as the `Auto-ingredient update mechanism` required the integration of +the Menus, Ingredients and Sales management system. An extensive knowledge of how each management works was therefore +essential. Analytical features also required substantial knowledge of front-end programming languages such as +https://www.w3schools.com/css/css_intro.asp[CSS] and https://www.w3schools.com/xml/xml_whatis.asp[XML]. + +* *Other contributions*: + +** Project Management: +*** Managed the integration and collaboration aspect of MeNUS. +https://github.com/CS2103-AY1819S1-F10-4/main/issues/147[#147] +*** Ensured https://en.wikipedia.org/wiki/Coupling_(computer_programming)[loose coupling] and flawless coordination +between various management systems of MeNUS. +*** Maintained issue tracker and merging of pull requests (PRs). +*** Oversaw the Storage component of MeNUS and preserved its functionality throughout. + +** Enhancements to existing features: +*** Updated MeNUS's Cascading Style Sheets (CSS) theme. + +** Quality Assurance: +*** Wrote unit and integration tests which helped improve the overall code coverage beyond 94%. + +** Documentation: +*** User Guide: +**** Added user-centric Sales command descriptions. +https://github.com/CS2103-AY1819S1-F10-4/main/pull/36[#36], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/119[#119], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/192[#192], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/294[#294] +*** Developer Guide: +**** Wrote implementation details for `display-sales` and `Auto-ingredient update` features. +https://github.com/CS2103-AY1819S1-F10-4/main/pull/119[#119], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/192[#192], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/294[#294] +**** Drew Unified Modeling Language (UML) diagrams under various sections. +https://github.com/CS2103-AY1819S1-F10-4/main/pull/141[#141], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/202[#202], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/266[#266] +**** Specified requirements under user stories and use cases +https://github.com/CS2103-AY1819S1-F10-4/main/pull/36[#36], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/119[#119], + +** Community: +*** https://bit.ly/2QAkHS9[Reviewed] + more than 75 PRs. +*** Ensured code quality through non-trivial comments and suggestions in many PRs. +https://github.com/CS2103-AY1819S1-F10-4/main/pull/267[#267] +https://github.com/CS2103-AY1819S1-F10-4/main/pull/256[#256] +https://github.com/CS2103-AY1819S1-F10-4/main/pull/250[#250] +https://github.com/CS2103-AY1819S1-F10-4/main/pull/189[#189] +https://github.com/CS2103-AY1819S1-F10-4/main/pull/170[#170] +https://github.com/CS2103-AY1819S1-F10-4/main/pull/262[#262] +https://github.com/CS2103-AY1819S1-F10-4/main/pull/122[#122] +*** Reported bugs and suggestions for other teams in the course. +https://github.com/CS2103-AY1819S1-W17-1/main/issues/239[#239] +https://github.com/CS2103-AY1819S1-W17-1/main/issues/240[#240] +https://github.com/CS2103-AY1819S1-W17-1/main/issues/241[#241] +https://github.com/CS2103-AY1819S1-W17-1/main/issues/242[#242] +https://github.com/CS2103-AY1819S1-W17-1/main/issues/243[#243] + +== Contributions to the User Guide + +|=== +|The following sections are my contributions to the User Guide. They reflect my ability to write clear and concise +documentation targeting non-technical readers. Please refer to https://cs2103-ay1819s1-f10-4-menus.netlify.com/userguide[User Guide] for more of my contributions. +The full version of my contribution can be found under section 5.6 and 5.7.5 - 5.7.8 of the User Guide. +|=== + +include::../UserGuide.adoc[tag=salesmanagement] + +include::../UserGuide.adoc[tag=rankdates] + + +== Contributions to the Developer Guide + +|=== +|The following sections are my contributions to the Developer Guide. They reflect my ability to convey technical +information in a comprehensible manner. It also showcases the technical depth of my contributions. +Please refer to https://cs2103-ay1819s1-f10-4-menus.netlify.com/developerguide[Developer Guide] for more of my +contributions. The full version of my contribution can be found under section 4.4, 4.5, and UC5XX of appendix A. +|=== + +include::../DeveloperGuide.adoc[tag=salesmanagement] + +include::../DeveloperGuide.adoc[tag=salesmanagement1] + +include::../DeveloperGuide.adoc[tag=ucsales] + + diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 453c2152ab9d..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,72 +0,0 @@ -= John Doe - Project Portfolio -:site-section: AboutUs -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 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. - -== Summary of contributions - -* *Major enhancement*: 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}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== Contributions to the Developer Guide - -|=== -|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ -|=== - -include::../DeveloperGuide.adoc[tag=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/m4dkip.adoc b/docs/team/m4dkip.adoc new file mode 100644 index 000000000000..f06cc3e931bd --- /dev/null +++ b/docs/team/m4dkip.adoc @@ -0,0 +1,73 @@ += Ong Ming Xian - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: MeNUS + +--- +https://github.com/CS2103-AY1819S1-F10-4/main[*MeNUS*] + +== Overview + +*MeNUS* is an all-in-one restaurant management desktop app designed to help restaurant owners +and managers streamline the process of managing restaurants within National University of Singapore (NUS). +With this app, users can manage their menus, sales, and even reservations! + +This portfolio showcases my contributions to this project. + +== Summary of Contributions +* *Code contributed*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=m4dkip[RepoSense] +* *Major enhancement*: Added the *Reservation Management* feature +** *What it does*: Allows the user to manage a Reservations List using various commands +** *Justification*: It can be messy to manually manage restaurant reservations using pen and paper. +This feature enables a systematic and reliable way to manage reservations. +** *Highlights*: This project required me to communicate with my teammates to integrate our different +management systems together. It required an in-depth analysis of the underlying *MeNUS* architecture. +** *Credits*: Dates are parsed using https://github.com/joestelmach/natty[Natty], a natural language date parser. + +* *Minor enhancement*: Added a way to add a command alias(a shortcut) to each command to allow users to type commands faster: +https://github.com/CS2103-AY1819S1-F10-4/main/pull/41[#41]. + +* *Other contributions*: + +** Project management: +*** Managed the documentation for the project team. +** Documentation: +*** Wrote feature descriptions in the User Guide and Developer Guide. +*** Helped teammates edit language and phrasing in the User Guide and Developer Guide. +** Community: +*** PRs reviewed: https://github.com/CS2103-AY1819S1-F10-4/main/pull/114[#114], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/211[#211], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/281[#281], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/282[#282], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/293[#293]. +*** Reported bugs and suggestions for other teams in the class: +https://github.com/CS2103-AY1819S1-T09-3/main/issues/144[#144], +https://github.com/CS2103-AY1819S1-T09-3/main/issues/147[#147], +https://github.com/CS2103-AY1819S1-T09-3/main/issues/162[#162]. +** Tools: +*** Integrated a third party library (https://github.com/joestelmach/natty[Natty]) to the project +https://github.com/CS2103-AY1819S1-F10-4/main/pull/250[#250]. + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide in relation to the `Reservations Management` feature. +They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=reservations] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide in relation to the `Reservations Management` feature. +They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=reservations] + +include::../DeveloperGuide.adoc[tag=ucreservations] + +include::../DeveloperGuide.adoc[tag=testreservations] diff --git a/docs/team/rebstan97.adoc b/docs/team/rebstan97.adoc new file mode 100644 index 000000000000..43ecaa3469ef --- /dev/null +++ b/docs/team/rebstan97.adoc @@ -0,0 +1,75 @@ += Rebecca Tan - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: MeNUS +This project portfolio documents my contributions towards *MeNUS* for the module CS2103T Software Engineering. + +== Overview + +*MeNUS* is an all-in-one restaurant management desktop application. It is designed for restaurant managers to manage + their menus, ingredients, sales and reservations conveniently and effectively - all in one place. It provides a + Command Line Interface (CLI) for restaurant managers to interact with, and has a Graphical User Interface (GUI) + created with JavaFX. It is written with approximately 30 kLOC. + +== Summary of Contributions +Below are my contributions in terms of feature enhancements, project management and documentation: + +* *Major enhancement*: Added *commands to keep track of the ingredient availability of a restuarant* +** *What it does*: +*** The `add-ingredient`, `delete-ingredient`, `edit-ingredient` and `list-ingredients` commands allow +the user to maintain a list of ingredients used by the restaurant. +*** The `low-stock` and `stockup` commands enable the +user to know which ingredients require stocking up and update the ingredient count as necessary. +*** The `select-ingredient` command lets the user view the details of each ingredient. + +** *Justification*: +*** Provide convenience: Restaurants use a large variety of ingredients daily, making it difficult to quickly +determine the availability of each ingredient. This feature reduces the need for restaurant managers + to manually do stock taking of its restaurant's ingredients. +*** Reduce food wastage: Restaurants often stock up in fixed quantities of ingredients, regardless of the actual +availability in the pantry. This leads to food wastage, if excess ingredients are not used. This feature provides +restaurant owners with more information about availability, so that they only stock up what is necessary. + +** *Highlights*: This enhancement involves adding several new commands to *MeNUS*. It requires understanding of the +underlying architecture and the components `logic`, `storage` and `model`. To maintain good abstraction, it + is also necessary to implement appropriate model interfaces so as to integrate the ingredient feature with other + features eg menu and sales management. + +* *Code contributed*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=rebstan97[RepoSense] + +* *Other contributions*: + +** Project management: +*** Maintained issue tracker +** Enhancements to existing features: +*** Enhanced the existing tagging feature by adding different colours for different tags +** Documentation: +*** Updated user stories and use cases in the Developer Guide (Pull request https://github.com/CS2103-AY1819S1-F10-4/main/pull/27[#27], https://github.com/CS2103-AY1819S1-F10-4/main/pull/118/files[#118]) +*** Added implementation of the commands `delete-ingredient` and `stockup` (Pull request https://github.com/CS2103-AY1819S1-F10-4/main/pull/116[#116], https://github.com/CS2103-AY1819S1-F10-4/main/pull/290[#290]) +*** Added description of ingredient commands to the User Guide(Pull request https://github.com/CS2103-AY1819S1-F10-4/main/pull/27[#27], https://github.com/CS2103-AY1819S1-F10-4/main/pull/116[#116]) +*** Made general enhancements to the User Guide (Pull request https://github.com/CS2103-AY1819S1-F10-4/main/pull/120[#120]) +*** Created initial UI mock-up (Pull request https://github.com/CS2103-AY1819S1-F10-4/main/pull/55[#55]) +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/CS2103-AY1819S1-F10-4/main/pull/122[#122], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/132[#132], https://github.com/CS2103-AY1819S1-F10-4/main/pull/183[#183] +*** Contributed to forum discussions (example: https://github.com/nus-cs2103-AY1819S1/forum/issues/48[1]) +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2103-AY1819S1-T12-3/main/issues/172[1], https://github.com/CS2103-AY1819S1-T12-3/main/issues/173[2], https://github.com/CS2103-AY1819S1-T12-3/main/issues/179[3], https://github.com/CS2103-AY1819S1-T12-3/main/issues/181[4], https://github.com/CS2103-AY1819S1-T12-3/main/issues/184[5]) + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=ingredientmanagement] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=ingredient] +include::../DeveloperGuide.adoc[tag=ucingredient] diff --git a/docs/team/yican95.adoc b/docs/team/yican95.adoc new file mode 100644 index 000000000000..35879bd24177 --- /dev/null +++ b/docs/team/yican95.adoc @@ -0,0 +1,92 @@ += Lee Yi Can - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: MeNUS + +This portfolio serves as a summary of my contributions for https://github.com/CS2103-AY1819S1-F10-4/main[MeNUS]. + +== Overview + +*MeNUS* is an all-in-one restaurant management desktop application developed to help restaurant owners +and managers streamline the process of managing restaurants. It allows the restaurant owners and managers to control +their inventory, menu, sales and reservations efficiently all-in-one place. +*MeNUS* is also designed to help restaurants save cost from running expensive and independent systems. + +Restaurant owners interact with *MeNUS* using a Command Line Interface +(CLI). It also has a Graphical User Interface(GUI) created with https://docs.oracle.com/javafx/2/[JavaFX]. *MeNUS* is + written using https://www.oracle.com/technetwork/java/javase/downloads/java-archive-javase9-3934878.html[Java 9] with +approximately 30 KLOC. + +== Summary of Contributions +* *Code contributed*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=yican95[RepoSense] + +* *Major enhancement*: Added *Menu Management* feature +** *What it does*: Enables the user to manage the menus with the various menu management commands. +** *Justification*: It can be messy and tedious to manage menus manually. This feature helps users to +streamline the process of managing menus by enabling them to modify the menus efficiently +with just a single command. +** *Highlights*: This enhancement integrates with some existing sales and ingredients management commands. +It requires an in-depth analysis and understanding of *MeNUS* architecture to ensure that commands of each management + feature is integrated properly together. + +* *Minor enhancement*: Added animation to the GUI +(Pull request https://github.com/CS2103-AY1819S1-F10-4/main/pull/214[#214]) +** *What it does*: Plays the https://docs.oracle.com/javafx/2/api/javafx/animation/FadeTransition.html[FadeTransition] +for a short duration when the user enters a command. + +* *Other contributions*: +** Project management: +*** Maintained issue tracker +*** Managed the integration of the features for *MeNUS*. Ensured that each feature was properly integrated into *MeNUS*. +** Enhancements to existing features: +*** Updated the JavaFX Cascading Style Sheets(CSS) theme (Pull request https://github.com/CS2103-AY1819S1-F10-4/main/pull/178[#178]) +** Documentation: +*** Added user stories, use cases and instructions for manual testing in the Developer Guide: +https://github.com/CS2103-AY1819S1-F10-4/main/pull/37[#37], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/114[#114], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/142[#142], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/282[#282] +*** Added menu management features description in https://cs2103-ay1819s1-f10-4.github.io/main/UserGuide.html[User Guide] and implementation in +https://cs2103-ay1819s1-f10-4.github.io/main/DeveloperGuide.html[Developer Guide] +** Community: +*** PRs reviewed (with non-trivial review comments): +https://github.com/CS2103-AY1819S1-F10-4/main/pull/179[#179], +https://github.com/CS2103-AY1819S1-F10-4/main/pull/183[#183] +*** Reported bugs and suggestions for other teams in the class: +https://github.com/CS2103-AY1819S1-W16-1/main/issues/138[#138], +https://github.com/CS2103-AY1819S1-W16-1/main/issues/142[#142], +https://github.com/CS2103-AY1819S1-W16-1/main/issues/145[#145], +https://github.com/CS2103-AY1819S1-W16-1/main/issues/156[#156] + +[[user]] +== Contributions to the User Guide + +|=== +|_The following sections below showcase extracts of my contributions to the User Guide and +my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=menuPart1] + +include::../UserGuide.adoc[tag=menuPart2] + +include::../UserGuide.adoc[tag=menuPart3] + +[[dev]] +== Contributions to the Developer Guide + +|=== +|_The following sections below showcase extracts of my contributions to the Developer Guide. They also showcase my +ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +=== Extracts of Implementation Section +include::../DeveloperGuide.adoc[tag=menu] + +=== Extracts of Use Case Section +include::../DeveloperGuide.adoc[tag=ucmenu] + +=== Extracts of Instructions for Manual Testing Section +include::../DeveloperGuide.adoc[tag=testMenu] diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java deleted file mode 100644 index 1deb3a1e4695..000000000000 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ /dev/null @@ -1,13 +0,0 @@ -package seedu.address.commons.core; - -/** - * Container for user visible messages. - */ -public class Messages { - - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; - public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - -} diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java deleted file mode 100644 index b72ad4740e5a..000000000000 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.commons.events.model; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.ReadOnlyAddressBook; - -/** Indicates the AddressBook in the model has changed*/ -public class AddressBookChangedEvent extends BaseEvent { - - public final ReadOnlyAddressBook data; - - public AddressBookChangedEvent(ReadOnlyAddressBook data) { - this.data = data; - } - - @Override - public String toString() { - return "number of persons " + data.getPersonList().size(); - } -} diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java deleted file mode 100644 index a890f8b47350..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java +++ /dev/null @@ -1,22 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.events.BaseEvent; - -/** - * Indicates a request to jump to the list of persons - */ -public class JumpToListRequestEvent extends BaseEvent { - - public final int targetIndex; - - public JumpToListRequestEvent(Index targetIndex) { - this.targetIndex = targetIndex.getZeroBased(); - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - -} diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java deleted file mode 100644 index c5c8b9ce90ed..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.person.Person; - -/** - * Represents a selection change in the Person List Panel - */ -public class PersonPanelSelectionChangedEvent extends BaseEvent { - - - private final Person newSelection; - - public PersonPanelSelectionChangedEvent(Person newSelection) { - this.newSelection = newSelection; - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - - public Person getNewSelection() { - return newSelection; - } -} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java deleted file mode 100644 index 8b34b862039a..000000000000 --- a/src/main/java/seedu/address/logic/Logic.java +++ /dev/null @@ -1,27 +0,0 @@ -package seedu.address.logic; - -import javafx.collections.ObservableList; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Person; - -/** - * API of the Logic component - */ -public interface Logic { - /** - * Executes the command and returns the result. - * @param commandText The command as entered by the user. - * @return the result of the command execution. - * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. - */ - CommandResult execute(String commandText) throws CommandException, ParseException; - - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); - - /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ - ListElementPointer getHistorySnapshot(); -} diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java deleted file mode 100644 index 9aff86fc33dc..000000000000 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ /dev/null @@ -1,52 +0,0 @@ -package seedu.address.logic; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * The main LogicManager of the app. - */ -public class LogicManager extends ComponentManager implements Logic { - private final Logger logger = LogsCenter.getLogger(LogicManager.class); - - private final Model model; - private final CommandHistory history; - private final AddressBookParser addressBookParser; - - public LogicManager(Model model) { - this.model = model; - history = new CommandHistory(); - addressBookParser = new AddressBookParser(); - } - - @Override - public CommandResult execute(String commandText) throws CommandException, ParseException { - logger.info("----------------[USER COMMAND][" + commandText + "]"); - try { - Command command = addressBookParser.parseCommand(commandText); - return command.execute(model, history); - } finally { - history.add(commandText); - } - } - - @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); - } - - @Override - public ListElementPointer getHistorySnapshot() { - return new ListElementPointer(history.getHistory()); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index d88e831ff1ce..000000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 1f85bcfe85a8..000000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.logic.CommandHistory; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(model); - model.resetData(new AddressBook()); - model.commitAddressBook(); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index a20e9d49eac7..000000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index dc782d8e230f..000000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,228 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.updatePerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index beb178e3a3f5..000000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 6d44824c7d1b..000000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java deleted file mode 100644 index 227771a4eef6..000000000000 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; - -/** - * Reverts the {@code model}'s address book to its previously undone state. - */ -public class RedoCommand extends Command { - - public static final String COMMAND_WORD = "redo"; - public static final String MESSAGE_SUCCESS = "Redo success!"; - public static final String MESSAGE_FAILURE = "No more commands to redo!"; - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (!model.canRedoAddressBook()) { - throw new CommandException(MESSAGE_FAILURE); - } - - model.redoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java deleted file mode 100644 index f5e8c1a8722e..000000000000 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Selects a person identified using it's displayed index from the address book. - */ -public class SelectCommand extends Command { - - public static final String COMMAND_WORD = "select"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; - - private final Index targetIndex; - - public SelectCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - List filteredPersonList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= filteredPersonList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); - - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof SelectCommand // instanceof handles nulls - && targetIndex.equals(((SelectCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java deleted file mode 100644 index 40441264f346..000000000000 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; - -/** - * Reverts the {@code model}'s address book to its previous state. - */ -public class UndoCommand extends Command { - - public static final String COMMAND_WORD = "undo"; - public static final String MESSAGE_SUCCESS = "Undo success!"; - public static final String MESSAGE_FAILURE = "No more commands to undo!"; - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (!model.canUndoAddressBook()) { - throw new CommandException(MESSAGE_FAILURE); - } - - model.undoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e83..000000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index b7d57f5db86a..000000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,92 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.HistoryCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.logic.commands.UndoCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case SelectCommand.COMMAND_WORD: - return new SelectCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case HistoryCommand.COMMAND_WORD: - return new HistoryCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - case UndoCommand.COMMAND_WORD: - return new UndoCommand(); - - case RedoCommand.COMMAND_WORD: - return new RedoCommand(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java deleted file mode 100644 index 5c9aebfa4888..000000000000 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ /dev/null @@ -1,148 +0,0 @@ -package seedu.address.logic.parser; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -/** - * 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/}.
- * 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.
- */ -public class ArgumentTokenizer { - - /** - * Tokenizes an arguments string and returns an {@code ArgumentMultimap} object that maps prefixes to their - * respective argument values. Only the given prefixes will be recognized in the arguments string. - * - * @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 - */ - public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { - List positions = findAllPrefixPositions(argsString, prefixes); - return extractArguments(argsString, positions); - } - - /** - * Finds all zero-based prefix positions in the given arguments string. - * - * @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 - */ - private static List findAllPrefixPositions(String argsString, Prefix... prefixes) { - return Arrays.stream(prefixes) - .flatMap(prefix -> findPrefixPositions(argsString, prefix).stream()) - .collect(Collectors.toList()); - } - - /** - * {@see findAllPrefixPositions} - */ - private static List findPrefixPositions(String argsString, Prefix prefix) { - List positions = new ArrayList<>(); - - int prefixPosition = findPrefixPosition(argsString, prefix.getPrefix(), 0); - while (prefixPosition != -1) { - PrefixPosition extendedPrefix = new PrefixPosition(prefix, prefixPosition); - positions.add(extendedPrefix); - prefixPosition = findPrefixPosition(argsString, prefix.getPrefix(), prefixPosition); - } - - return positions; - } - - /** - * Returns the index of the first occurrence of {@code prefix} in - * {@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 - * {@code argsString} = "e/hi p/900", {@code prefix} = "p/" and - * {@code fromIndex} = 0, this method returns 5. - */ - private static int findPrefixPosition(String argsString, String prefix, int fromIndex) { - int prefixIndex = argsString.indexOf(" " + prefix, fromIndex); - return prefixIndex == -1 ? -1 - : prefixIndex + 1; // +1 as offset for whitespace - } - - /** - * Extracts prefixes and their argument values, and returns an {@code ArgumentMultimap} object that maps the - * extracted prefixes to their respective arguments. Prefixes are extracted based on their zero-based positions in - * {@code argsString}. - * - * @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 - */ - private static ArgumentMultimap extractArguments(String argsString, List prefixPositions) { - - // Sort by start position - prefixPositions.sort((prefix1, prefix2) -> prefix1.getStartPosition() - prefix2.getStartPosition()); - - // Insert a PrefixPosition to represent the preamble - PrefixPosition preambleMarker = new PrefixPosition(new Prefix(""), 0); - prefixPositions.add(0, preambleMarker); - - // Add a dummy PrefixPosition to represent the end of the string - PrefixPosition endPositionMarker = new PrefixPosition(new Prefix(""), argsString.length()); - prefixPositions.add(endPositionMarker); - - // Map prefixes to their argument values (if any) - ArgumentMultimap argMultimap = new ArgumentMultimap(); - for (int i = 0; i < prefixPositions.size() - 1; i++) { - // Extract and store prefixes and their arguments - Prefix argPrefix = prefixPositions.get(i).getPrefix(); - String argValue = extractArgumentValue(argsString, prefixPositions.get(i), prefixPositions.get(i + 1)); - argMultimap.put(argPrefix, argValue); - } - - return argMultimap; - } - - /** - * Returns the trimmed value of the argument in the arguments string specified by {@code currentPrefixPosition}. - * The end position of the value is determined by {@code nextPrefixPosition}. - */ - private static String extractArgumentValue(String argsString, - PrefixPosition currentPrefixPosition, - PrefixPosition nextPrefixPosition) { - Prefix prefix = currentPrefixPosition.getPrefix(); - - int valueStartPos = currentPrefixPosition.getStartPosition() + prefix.getPrefix().length(); - String value = argsString.substring(valueStartPos, nextPrefixPosition.getStartPosition()); - - return value.trim(); - } - - /** - * Represents a prefix's position in an arguments string. - */ - private static class PrefixPosition { - private int startPosition; - private final Prefix prefix; - - PrefixPosition(Prefix prefix, int startPosition) { - this.prefix = prefix; - this.startPosition = startPosition; - } - - int getStartPosition() { - return startPosition; - } - - Prefix getPrefix() { - return prefix; - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java deleted file mode 100644 index 75b1a9bf1190..000000000000 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.logic.parser; - -/** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands - */ -public class CliSyntax { - - /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); - -} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 4d1f4bb0e4ec..000000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns an DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea1..000000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index b186a967cb94..000000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns an FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java deleted file mode 100644 index 76daf40807e2..000000000000 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ /dev/null @@ -1,124 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods used for parsing strings in the various *Parser classes. - */ -public class ParserUtil { - - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; - - /** - * 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 { - String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); - } - return Index.fromOneBased(Integer.parseInt(trimmedIndex)); - } - - /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code name} is invalid. - */ - public static Name parseName(String name) throws ParseException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_NAME_CONSTRAINTS); - } - return new Name(trimmedName); - } - - /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code phone} is invalid. - */ - public static Phone parsePhone(String phone) throws ParseException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new ParseException(Phone.MESSAGE_PHONE_CONSTRAINTS); - } - return new Phone(trimmedPhone); - } - - /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code address} is invalid. - */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_ADDRESS_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - - /** - * Parses a {@code String email} into an {@code Email}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code email} is invalid. - */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_EMAIL_CONSTRAINTS); - } - return new Email(trimmedEmail); - } - - /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code tag} is invalid. - */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_TAG_CONSTRAINTS); - } - return new Tag(trimmedTag); - } - - /** - * Parses {@code Collection tags} into a {@code Set}. - */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); - } - return tagSet; - } -} diff --git a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java b/src/main/java/seedu/address/logic/parser/SelectCommandParser.java deleted file mode 100644 index 565b7f04bfe1..000000000000 --- a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java +++ /dev/null @@ -1,28 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new SelectCommand object - */ -public class SelectCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the SelectCommand - * and returns an SelectCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public SelectCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new SelectCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE), pe); - } - } -} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 7f85c8b9258b..000000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void updatePerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index ac4521f33199..000000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,78 +0,0 @@ -package seedu.address.model; - -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * The API of the Model component. - */ -public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - - /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - boolean hasPerson(Person person); - - /** - * Deletes the given person. - * The person must exist in the address book. - */ - void deletePerson(Person target); - - /** - * Adds the given person. - * {@code person} must not already exist in the address book. - */ - void addPerson(Person person); - - /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - void updatePerson(Person target, Person editedPerson); - - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); - - /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. - */ - void updateFilteredPersonList(Predicate predicate); - - /** - * Returns true if the model has previous address book states to restore. - */ - boolean canUndoAddressBook(); - - /** - * Returns true if the model has undone address book states to restore. - */ - boolean canRedoAddressBook(); - - /** - * Restores the model's address book to its previous state. - */ - void undoAddressBook(); - - /** - * Restores the model's address book to its previously undone state. - */ - void redoAddressBook(); - - /** - * Saves the current address book state for undo/redo. - */ - void commitAddressBook(); -} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java deleted file mode 100644 index a664602ef5b1..000000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,150 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -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.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.model.person.Person; - -/** - * Represents the in-memory model of the address book data. - */ -public class ModelManager extends ComponentManager implements Model { - private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - - private final VersionedAddressBook versionedAddressBook; - private final FilteredList filteredPersons; - - /** - * Initializes a ModelManager with the given addressBook and userPrefs. - */ - public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { - super(); - requireAllNonNull(addressBook, userPrefs); - - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - - versionedAddressBook = new VersionedAddressBook(addressBook); - filteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - @Override - public void resetData(ReadOnlyAddressBook newData) { - versionedAddressBook.resetData(newData); - indicateAddressBookChanged(); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return versionedAddressBook; - } - - /** Raises an event to indicate the model has changed */ - private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(versionedAddressBook)); - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return versionedAddressBook.hasPerson(person); - } - - @Override - public void deletePerson(Person target) { - versionedAddressBook.removePerson(target); - indicateAddressBookChanged(); - } - - @Override - public void addPerson(Person person) { - versionedAddressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - indicateAddressBookChanged(); - } - - @Override - public void updatePerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - versionedAddressBook.updatePerson(target, editedPerson); - indicateAddressBookChanged(); - } - - //=========== Filtered Person List Accessors ============================================================= - - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} - */ - @Override - public ObservableList getFilteredPersonList() { - return FXCollections.unmodifiableObservableList(filteredPersons); - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - requireNonNull(predicate); - filteredPersons.setPredicate(predicate); - } - - //=========== Undo/Redo ================================================================================= - - @Override - public boolean canUndoAddressBook() { - return versionedAddressBook.canUndo(); - } - - @Override - public boolean canRedoAddressBook() { - return versionedAddressBook.canRedo(); - } - - @Override - public void undoAddressBook() { - versionedAddressBook.undo(); - indicateAddressBookChanged(); - } - - @Override - public void redoAddressBook() { - versionedAddressBook.redo(); - indicateAddressBookChanged(); - } - - @Override - public void commitAddressBook() { - versionedAddressBook.commit(); - } - - @Override - public boolean equals(Object obj) { - // short circuit if same object - if (obj == this) { - return true; - } - - // instanceof handles nulls - if (!(obj instanceof ModelManager)) { - return false; - } - - // state check - ModelManager other = (ModelManager) obj; - return versionedAddressBook.equals(other.versionedAddressBook) - && filteredPersons.equals(other.filteredPersons); - } - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a290..000000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedAddressBook.java deleted file mode 100644 index 227a335045d7..000000000000 --- a/src/main/java/seedu/address/model/VersionedAddressBook.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.model; - -import java.util.ArrayList; -import java.util.List; - -/** - * {@code AddressBook} that keeps track of its own history. - */ -public class VersionedAddressBook extends AddressBook { - - private final List addressBookStateList; - private int currentStatePointer; - - public VersionedAddressBook(ReadOnlyAddressBook initialState) { - super(initialState); - - addressBookStateList = new ArrayList<>(); - addressBookStateList.add(new AddressBook(initialState)); - currentStatePointer = 0; - } - - /** - * Saves a copy of the current {@code AddressBook} state at the end of the state list. - * Undone states are removed from the state list. - */ - public void commit() { - removeStatesAfterCurrentPointer(); - addressBookStateList.add(new AddressBook(this)); - currentStatePointer++; - } - - private void removeStatesAfterCurrentPointer() { - addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear(); - } - - /** - * Restores the address book to its previous state. - */ - public void undo() { - if (!canUndo()) { - throw new NoUndoableStateException(); - } - currentStatePointer--; - resetData(addressBookStateList.get(currentStatePointer)); - } - - /** - * Restores the address book to its previously undone state. - */ - public void redo() { - if (!canRedo()) { - throw new NoRedoableStateException(); - } - currentStatePointer++; - resetData(addressBookStateList.get(currentStatePointer)); - } - - /** - * Returns true if {@code undo()} has address book states to undo. - */ - public boolean canUndo() { - return currentStatePointer > 0; - } - - /** - * Returns true if {@code redo()} has address book states to redo. - */ - public boolean canRedo() { - return currentStatePointer < addressBookStateList.size() - 1; - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof VersionedAddressBook)) { - return false; - } - - VersionedAddressBook otherVersionedAddressBook = (VersionedAddressBook) other; - - // state check - return super.equals(otherVersionedAddressBook) - && addressBookStateList.equals(otherVersionedAddressBook.addressBookStateList) - && currentStatePointer == otherVersionedAddressBook.currentStatePointer; - } - - /** - * Thrown when trying to {@code undo()} but can't. - */ - public static class NoUndoableStateException extends RuntimeException { - private NoUndoableStateException() { - super("Current state pointer at start of addressBookState list, unable to undo."); - } - } - - /** - * Thrown when trying to {@code redo()} but can't. - */ - public static class NoRedoableStateException extends RuntimeException { - private NoRedoableStateException() { - super("Current state pointer at end of addressBookState list, unable to redo."); - } - } -} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index a1409233ceb9..000000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,58 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_ADDRESS_CONSTRAINTS = - "Addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String ADDRESS_VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_ADDRESS_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(ADDRESS_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index 38a7629e9a2d..000000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; - public static final String MESSAGE_EMAIL_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n" - + "2. This is followed by a '@' and then a domain name. " - + "The domain name must:\n" - + " - be at least 2 characters long\n" - + " - start and end with alphanumeric characters\n" - + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any."; - // alphanumeric and special characters - private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; - private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore - private static final String DOMAIN_MIDDLE_REGEX = "[a-zA-Z0-9.-]*"; // alphanumeric, period and hyphen - private static final String DOMAIN_LAST_CHARACTER_REGEX = "[^\\W_]$"; - public static final String EMAIL_VALIDATION_REGEX = LOCAL_PART_REGEX + "@" - + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_EMAIL_CONSTRAINTS); - value = email; - } - - /** - * Returns if a given string is a valid email. - */ - public static boolean isValidEmail(String test) { - return test.matches(EMAIL_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 557a7a60cd51..000000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons of the same name have at least one other identity field that is the same. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index a22e51653835..000000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -public class Phone { - - - public static final String MESSAGE_PHONE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String PHONE_VALIDATION_REGEX = "\\d{3,}"; - public final String value; - - /** - * Constructs a {@code Phone}. - * - * @param phone A valid phone number. - */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_PHONE_CONSTRAINTS); - value = phone; - } - - /** - * Returns true if a given string is a valid phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(PHONE_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Phone // instanceof handles nulls - && value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 5856aa42e6b5..000000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,135 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return FXCollections.unmodifiableObservableList(internalList); - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f594423..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca73..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java deleted file mode 100644 index 1806da4facfa..000000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.model.util; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods for populating {@code AddressBook} with sample data. - */ -public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; - } - - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - return sampleAb; - } - - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - -} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f92..000000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index 28791127999b..000000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,39 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; - -/** - * API of the Storage component - */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { - - @Override - Optional readUserPrefs() throws DataConversionException, IOException; - - @Override - void saveUserPrefs(UserPrefs userPrefs) throws IOException; - - @Override - Path getAddressBookFilePath(); - - @Override - Optional readAddressBook() throws DataConversionException, IOException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * Saves the current version of the Address Book to the hard disk. - * Creates the data file if it is missing. - * Raises {@link DataSavingExceptionEvent} if there was an error during saving. - */ - void handleAddressBookChangedEvent(AddressBookChangedEvent abce); -} diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java deleted file mode 100644 index b0df908a76a7..000000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,93 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager extends ComponentManager implements Storage { - - private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; - - - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - super(); - this.addressBookStorage = addressBookStorage; - this.userPrefsStorage = userPrefsStorage; - } - - // ================ UserPrefs methods ============================== - - @Override - public Path getUserPrefsFilePath() { - return userPrefsStorage.getUserPrefsFilePath(); - } - - @Override - public Optional readUserPrefs() throws DataConversionException, IOException { - return userPrefsStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(UserPrefs userPrefs) throws IOException { - userPrefsStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { - logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); - } - - - @Override - @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); - try { - saveAddressBook(event.data); - } catch (IOException e) { - raise(new DataSavingExceptionEvent(e)); - } - } - -} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java deleted file mode 100644 index c03785e5700f..000000000000 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * JAXB-friendly version of the Person. - */ -public class XmlAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - @XmlElement(required = true) - private String name; - @XmlElement(required = true) - private String phone; - @XmlElement(required = true) - private String email; - @XmlElement(required = true) - private String address; - - @XmlElement - private List tagged = new ArrayList<>(); - - /** - * Constructs an XmlAdaptedPerson. - * This is the no-arg constructor that is required by JAXB. - */ - public XmlAdaptedPerson() {} - - /** - * Constructs an {@code XmlAdaptedPerson} with the given person details. - */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged = new ArrayList<>(tagged); - } - } - - /** - * Converts a given Person into this class for JAXB use. - * - * @param source future changes to this will not affect the created XmlAdaptedPerson - */ - public XmlAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged = source.getTags().stream() - .map(XmlAdaptedTag::new) - .collect(Collectors.toList()); - } - - /** - * Converts this jaxb-friendly adapted person object into the model's Person object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (XmlAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlAdaptedPerson)) { - return false; - } - - XmlAdaptedPerson otherPerson = (XmlAdaptedPerson) other; - return Objects.equals(name, otherPerson.name) - && Objects.equals(phone, otherPerson.phone) - && Objects.equals(email, otherPerson.email) - && Objects.equals(address, otherPerson.address) - && tagged.equals(otherPerson.tagged); - } -} diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java deleted file mode 100644 index ecf0e7ec23a8..000000000000 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as an xml file on the hard disk. - */ -public class XmlAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(XmlAddressBookStorage.class); - - private Path filePath; - - public XmlAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()} - * @param filePath location of the data. Cannot be null - * @throws DataConversionException if the file is not in the correct format. - */ - public Optional readAddressBook(Path filePath) throws DataConversionException, - FileNotFoundException { - requireNonNull(filePath); - - if (!Files.exists(filePath)) { - logger.info("AddressBook file " + filePath + " not found"); - return Optional.empty(); - } - - XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(filePath); - try { - return Optional.of(xmlAddressBook.toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} - * @param filePath location of the data. Cannot be null - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - FileUtil.createIfMissing(filePath); - XmlFileStorage.saveDataToFile(filePath, new XmlSerializableAddressBook(addressBook)); - } - -} diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java deleted file mode 100644 index b85fa4a8f07e..000000000000 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ /dev/null @@ -1,71 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to XML format - */ -@XmlRootElement(name = "addressbook") -public class XmlSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - @XmlElement - private List persons; - - /** - * Creates an empty XmlSerializableAddressBook. - * This empty constructor is required for marshalling. - */ - public XmlSerializableAddressBook() { - persons = new ArrayList<>(); - } - - /** - * Conversion - */ - public XmlSerializableAddressBook(ReadOnlyAddressBook src) { - this(); - persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this addressbook into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated or duplicates in the - * {@code XmlAdaptedPerson}. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (XmlAdaptedPerson p : persons) { - Person person = p.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlSerializableAddressBook)) { - return false; - } - return persons.equals(((XmlSerializableAddressBook) other).persons); - } -} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java deleted file mode 100644 index 0e361a4d7baf..000000000000 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ /dev/null @@ -1,203 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.scene.control.MenuItem; -import javafx.scene.control.TextInputControl; -import javafx.scene.input.KeyCombination; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.StackPane; -import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -import seedu.address.commons.events.ui.ShowHelpRequestEvent; -import seedu.address.logic.Logic; -import seedu.address.model.UserPrefs; - -/** - * The Main Window. Provides the basic application layout containing - * a menu bar and space where other JavaFX elements can be placed. - */ -public class MainWindow extends UiPart { - - private static final String FXML = "MainWindow.fxml"; - - private final Logger logger = LogsCenter.getLogger(getClass()); - - private Stage primaryStage; - private Logic logic; - - // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; - private PersonListPanel personListPanel; - private Config config; - private UserPrefs prefs; - private HelpWindow helpWindow; - - @FXML - private StackPane browserPlaceholder; - - @FXML - private StackPane commandBoxPlaceholder; - - @FXML - private MenuItem helpMenuItem; - - @FXML - private StackPane personListPanelPlaceholder; - - @FXML - private StackPane resultDisplayPlaceholder; - - @FXML - private StackPane statusbarPlaceholder; - - public MainWindow(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { - super(FXML, primaryStage); - - // Set dependencies - this.primaryStage = primaryStage; - this.logic = logic; - this.config = config; - this.prefs = prefs; - - // Configure the UI - setTitle(config.getAppTitle()); - setWindowDefaultSize(prefs); - - setAccelerators(); - registerAsAnEventHandler(this); - - helpWindow = new HelpWindow(); - } - - public Stage getPrimaryStage() { - return primaryStage; - } - - private void setAccelerators() { - setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); - } - - /** - * Sets the accelerator of a MenuItem. - * @param keyCombination the KeyCombination value of the accelerator - */ - private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { - menuItem.setAccelerator(keyCombination); - - /* - * TODO: the code below can be removed once the bug reported here - * https://bugs.openjdk.java.net/browse/JDK-8131666 - * is fixed in later version of SDK. - * - * According to the bug report, TextInputControl (TextField, TextArea) will - * consume function-key events. Because CommandBox contains a TextField, and - * ResultDisplay contains a TextArea, thus some accelerators (e.g F1) will - * not work when the focus is in them because the key event is consumed by - * the TextInputControl(s). - * - * For now, we add following event filter to capture such key events and open - * help window purposely so to support accelerators even when focus is - * in CommandBox or ResultDisplay. - */ - getRoot().addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getTarget() instanceof TextInputControl && keyCombination.match(event)) { - menuItem.getOnAction().handle(new ActionEvent()); - event.consume(); - } - }); - } - - /** - * Fills up all the placeholders of this window. - */ - void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); - - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); - - ResultDisplay resultDisplay = new ResultDisplay(); - resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - - StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath()); - statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); - - CommandBox commandBox = new CommandBox(logic); - commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); - } - - void hide() { - primaryStage.hide(); - } - - private void setTitle(String appTitle) { - primaryStage.setTitle(appTitle); - } - - /** - * Sets the default size based on user preferences. - */ - private void setWindowDefaultSize(UserPrefs prefs) { - primaryStage.setHeight(prefs.getGuiSettings().getWindowHeight()); - primaryStage.setWidth(prefs.getGuiSettings().getWindowWidth()); - if (prefs.getGuiSettings().getWindowCoordinates() != null) { - primaryStage.setX(prefs.getGuiSettings().getWindowCoordinates().getX()); - primaryStage.setY(prefs.getGuiSettings().getWindowCoordinates().getY()); - } - } - - /** - * Returns the current size and the position of the main Window. - */ - GuiSettings getCurrentGuiSetting() { - return new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), - (int) primaryStage.getX(), (int) primaryStage.getY()); - } - - /** - * Opens the help window or focuses on it if it's already opened. - */ - @FXML - public void handleHelp() { - if (!helpWindow.isShowing()) { - helpWindow.show(); - } else { - helpWindow.focus(); - } - } - - void show() { - primaryStage.show(); - } - - /** - * Closes the application. - */ - @FXML - private void handleExit() { - raise(new ExitAppRequestEvent()); - } - - public PersonListPanel getPersonListPanel() { - return personListPanel; - } - - void releaseResources() { - browserPanel.freeResources(); - } - - @Subscribe - private void handleShowHelpEvent(ShowHelpRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - handleHelp(); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index 80080adb4305..000000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,83 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -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.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - public PersonListPanel(ObservableList personList) { - super(FXML); - setConnections(personList); - registerAsAnEventHandler(this); - } - - private void setConnections(ObservableList personList) { - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - setEventHandlerForSelectionChangeEvent(); - } - - private void setEventHandlerForSelectionChangeEvent() { - personListView.getSelectionModel().selectedItemProperty() - .addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - logger.fine("Selection in person list panel changed to : '" + newValue + "'"); - raise(new PersonPanelSelectionChangedEvent(newValue)); - } - }); - } - - /** - * Scrolls to the {@code PersonCard} at the {@code index} and selects it. - */ - private void scrollTo(int index) { - Platform.runLater(() -> { - personListView.scrollTo(index); - personListView.getSelectionModel().clearAndSelect(index); - }); - } - - @Subscribe - private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - scrollTo(event.targetIndex); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/seedu/restaurant/AppParameters.java similarity index 93% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/seedu/restaurant/AppParameters.java index ab552c398f3d..9eeba0a8ce8a 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/seedu/restaurant/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.restaurant; import java.nio.file.Path; import java.nio.file.Paths; @@ -7,8 +7,8 @@ import java.util.logging.Logger; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.FileUtil; +import seedu.restaurant.commons.core.LogsCenter; +import seedu.restaurant.commons.util.FileUtil; /** * Represents the parsed command-line parameters given to the application. diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/restaurant/MainApp.java similarity index 63% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/seedu/restaurant/MainApp.java index ecdd043a4f81..fae8fe94ff25 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/restaurant/MainApp.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.restaurant; import java.io.IOException; import java.nio.file.Path; @@ -10,37 +10,36 @@ import javafx.application.Application; import javafx.application.Platform; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; -import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.storage.XmlAddressBookStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; +import seedu.restaurant.commons.core.Config; +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.LogsCenter; +import seedu.restaurant.commons.core.Version; +import seedu.restaurant.commons.events.ui.ExitAppRequestEvent; +import seedu.restaurant.commons.exceptions.DataConversionException; +import seedu.restaurant.commons.util.ConfigUtil; +import seedu.restaurant.commons.util.StringUtil; +import seedu.restaurant.logic.Logic; +import seedu.restaurant.logic.LogicManager; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ModelManager; +import seedu.restaurant.model.ReadOnlyRestaurantBook; +import seedu.restaurant.model.UserPrefs; +import seedu.restaurant.model.util.SampleDataUtil; +import seedu.restaurant.storage.JsonUserPrefsStorage; +import seedu.restaurant.storage.RestaurantBookStorage; +import seedu.restaurant.storage.Storage; +import seedu.restaurant.storage.StorageManager; +import seedu.restaurant.storage.UserPrefsStorage; +import seedu.restaurant.storage.XmlRestaurantBookStorage; +import seedu.restaurant.ui.Ui; +import seedu.restaurant.ui.UiManager; /** * The main entry point to the application. */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 4, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -54,7 +53,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing MeNUS ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -62,8 +61,9 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new XmlAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + RestaurantBookStorage restaurantBookStorage = new XmlRestaurantBookStorage( + userPrefs.getRestaurantBookFilePath()); + storage = new StorageManager(restaurantBookStorage, userPrefsStorage); initLogging(config); @@ -77,28 +77,29 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s restaurant book and {@code userPrefs}.
+ * The data from the sample restaurant book will be used instead if {@code storage}'s restaurant book is not found, + * or an empty restaurant book will be used instead if errors occur when reading {@code storage}'s restaurant book. */ private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional restaurantBookOptional; + ReadOnlyRestaurantBook initialRestaurantBookData; + try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + restaurantBookOptional = storage.readRestaurantBook(); + if (!restaurantBookOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a default RestaurantBook"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialRestaurantBookData = restaurantBookOptional.orElseGet(SampleDataUtil::getSampleRestaurantBook); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with a default RestaurantBook"); + initialRestaurantBookData = SampleDataUtil.getSampleRestaurantBook(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with a default RestaurantBook"); + initialRestaurantBookData = SampleDataUtil.getSampleRestaurantBook(); } - return new ModelManager(initialData, userPrefs); + return new ModelManager(initialRestaurantBookData, userPrefs); } private void initLogging(Config config) { @@ -106,9 +107,8 @@ private void initLogging(Config config) { } /** - * Returns a {@code Config} using the file at {@code configFilePath}.
- * The default file path {@code Config#DEFAULT_CONFIG_FILE} will be used instead - * if {@code configFilePath} is null. + * Returns a {@code Config} using the file at {@code configFilePath}.
The default file path {@code + * Config#DEFAULT_CONFIG_FILE} will be used instead if {@code configFilePath} is null. */ protected Config initConfig(Path configFilePath) { Config initializedConfig; @@ -142,9 +142,8 @@ protected Config initConfig(Path configFilePath) { } /** - * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path, - * or a new {@code UserPrefs} with default configuration if errors occur when - * reading from the file. + * Returns a {@code UserPrefs} using the file at {@code storage}'s user prefs file path, or a new {@code UserPrefs} + * with default configuration if errors occur when reading from the file. */ protected UserPrefs initPrefs(UserPrefsStorage storage) { Path prefsFilePath = storage.getUserPrefsFilePath(); @@ -159,7 +158,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty RestaurantBook"); initializedPrefs = new UserPrefs(); } @@ -179,13 +178,13 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting RestaurantBook " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping MeNUS ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); diff --git a/src/main/java/seedu/address/commons/core/ComponentManager.java b/src/main/java/seedu/restaurant/commons/core/ComponentManager.java similarity index 86% rename from src/main/java/seedu/address/commons/core/ComponentManager.java rename to src/main/java/seedu/restaurant/commons/core/ComponentManager.java index 05a400773ae8..91bf188ccf5a 100644 --- a/src/main/java/seedu/address/commons/core/ComponentManager.java +++ b/src/main/java/seedu/restaurant/commons/core/ComponentManager.java @@ -1,6 +1,6 @@ -package seedu.address.commons.core; +package seedu.restaurant.commons.core; -import seedu.address.commons.events.BaseEvent; +import seedu.restaurant.commons.events.BaseEvent; /** * Base class for *Manager classes diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/restaurant/commons/core/Config.java similarity index 95% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/seedu/restaurant/commons/core/Config.java index e978d621e086..8805c4554eee 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/restaurant/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.restaurant.commons.core; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,7 +13,7 @@ public class Config { public static final Path DEFAULT_CONFIG_FILE = Paths.get("config.json"); // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "MeNUS"; private Level logLevel = Level.INFO; private Path userPrefsFilePath = Paths.get("preferences.json"); diff --git a/src/main/java/seedu/address/commons/core/EventsCenter.java b/src/main/java/seedu/restaurant/commons/core/EventsCenter.java similarity index 91% rename from src/main/java/seedu/address/commons/core/EventsCenter.java rename to src/main/java/seedu/restaurant/commons/core/EventsCenter.java index 799b976f7eb7..f75d8980e3f9 100644 --- a/src/main/java/seedu/address/commons/core/EventsCenter.java +++ b/src/main/java/seedu/restaurant/commons/core/EventsCenter.java @@ -1,10 +1,10 @@ -package seedu.address.commons.core; +package seedu.restaurant.commons.core; import java.util.logging.Logger; import com.google.common.eventbus.EventBus; -import seedu.address.commons.events.BaseEvent; +import seedu.restaurant.commons.events.BaseEvent; /** * Manages the event dispatching of the app. diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/restaurant/commons/core/GuiSettings.java similarity index 98% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/seedu/restaurant/commons/core/GuiSettings.java index ed578f1a9bb6..59bf7fa5c7cf 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/restaurant/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.restaurant.commons.core; import java.awt.Point; import java.io.Serializable; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/restaurant/commons/core/LogsCenter.java similarity index 96% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/seedu/restaurant/commons/core/LogsCenter.java index 5316a1d87d3e..242d4730beee 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/restaurant/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.restaurant.commons.core; import java.io.IOException; import java.util.Arrays; @@ -8,7 +8,7 @@ import java.util.logging.Logger; import java.util.logging.SimpleFormatter; -import seedu.address.commons.events.BaseEvent; +import seedu.restaurant.commons.events.BaseEvent; /** * Configures and manages loggers and handlers, including their logging level @@ -20,7 +20,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "restaurantbook.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/seedu/restaurant/commons/core/Messages.java b/src/main/java/seedu/restaurant/commons/core/Messages.java new file mode 100644 index 000000000000..c8ef013c71ba --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/core/Messages.java @@ -0,0 +1,33 @@ +package seedu.restaurant.commons.core; + +/** + * Container for user visible messages that is not tied to any commands in specific. + */ +public class Messages { + + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command. To view the list of commands, enter `help`"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format!\n%1$s"; + + // Account Management + public static final String MESSAGE_COMMAND_FORBIDDEN = "Please login to execute privileged commands. For more " + + "information, enter `help`."; + public static final String MESSAGE_INVALID_ACCOUNT_DISPLAYED_INDEX = "The account index provided is invalid"; + + // Ingredient Management + public static final String MESSAGE_INVALID_INGREDIENT_DISPLAYED_INDEX = "The ingredient index provided is invalid"; + public static final String MESSAGE_INGREDIENT_NAME_NOT_FOUND = "The ingredient name provided does not exist"; + public static final String MESSAGE_STOCKUP_INGREDIENT_NOT_FOUND = "One or more of the ingredient names provided " + + "do not exist"; + + // Menu Management + public static final String MESSAGE_INVALID_ITEM_DISPLAYED_INDEX = "The item index provided is invalid"; + public static final String MESSAGE_ITEMS_LISTED_OVERVIEW = "%1$d items listed!"; + public static final String MESSAGE_NAME_NOT_FOUND = "The name provided does not belong to the items in the menu"; + + // Sales Management + public static final String MESSAGE_INVALID_RECORD_DISPLAYED_INDEX = "The sales record index provided is invalid."; + + // Reservation Management + public static final String MESSAGE_INVALID_RESERVATION_DISPLAYED_INDEX = "The reservation index provided is " + + "invalid"; +} diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/restaurant/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/seedu/restaurant/commons/core/Version.java index e117f91b3b2e..d3efe758c539 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/restaurant/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.restaurant.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/restaurant/commons/core/index/Index.java similarity index 97% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/seedu/restaurant/commons/core/index/Index.java index 19536439c099..418d8348ad94 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/restaurant/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package seedu.restaurant.commons.core.index; /** * Represents a zero-based or one-based index. diff --git a/src/main/java/seedu/restaurant/commons/core/session/UserSession.java b/src/main/java/seedu/restaurant/commons/core/session/UserSession.java new file mode 100644 index 000000000000..0d101a382c21 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/core/session/UserSession.java @@ -0,0 +1,69 @@ +package seedu.restaurant.commons.core.session; + +import seedu.restaurant.model.account.Account; + +//@@author AZhiKai +/** + * Since this is a local desktop application which may or may not work with internet connection, we assume the + * multiplicity of 0...1 user will be logged in at any time. Thus, it does not make sense to create a list of user + * sessions as it is impossible in this project's context. + */ +public class UserSession { + + private static boolean isAuthenticated = false; + private static Account account; + + // This class should not be instantiated. + private UserSession() { + throw new AssertionError("UserSession should not be instantiated."); + } + + /** + * Stores this {@code Account} info as part of this session. + * + * @param acc logged in for this session. + */ + public static void create(Account acc) { + if (!isAuthenticated) { + isAuthenticated = true; + account = acc; + } + } + + /** + * Logs out of this account which releases this session. + */ + public static void destroy() { + isAuthenticated = false; + account = null; + } + + /** + * Updates the session with the new account info, such as updating of account password. + * + * @param acc that has been updated. + */ + public static void update(Account acc) { + if (isAuthenticated) { + account = acc; + } + } + + /** + * Checks if this session exists. + * + * @return true if this session exists. Otherwise, false. + */ + public static boolean isAuthenticated() { + return isAuthenticated && account != null; + } + + /** + * Gets the account that is logged in for this session. + * + * @return a {@code Account} object. + */ + public static Account getAccount() { + return account; + } +} diff --git a/src/main/java/seedu/address/commons/events/BaseEvent.java b/src/main/java/seedu/restaurant/commons/events/BaseEvent.java similarity index 90% rename from src/main/java/seedu/address/commons/events/BaseEvent.java rename to src/main/java/seedu/restaurant/commons/events/BaseEvent.java index 85e71cbb6b62..e50d4e8f1f0d 100644 --- a/src/main/java/seedu/address/commons/events/BaseEvent.java +++ b/src/main/java/seedu/restaurant/commons/events/BaseEvent.java @@ -1,4 +1,4 @@ -package seedu.address.commons.events; +package seedu.restaurant.commons.events; /** * The base class for all event classes. diff --git a/src/main/java/seedu/restaurant/commons/events/model/RestaurantBookChangedEvent.java b/src/main/java/seedu/restaurant/commons/events/model/RestaurantBookChangedEvent.java new file mode 100644 index 000000000000..c1832089a623 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/model/RestaurantBookChangedEvent.java @@ -0,0 +1,25 @@ +package seedu.restaurant.commons.events.model; + +import seedu.restaurant.commons.events.BaseEvent; +import seedu.restaurant.model.ReadOnlyRestaurantBook; + +/** + * Indicates the RestaurantBook in the model has changed + */ +public class RestaurantBookChangedEvent extends BaseEvent { + + public final ReadOnlyRestaurantBook data; + + public RestaurantBookChangedEvent(ReadOnlyRestaurantBook data) { + this.data = data; + } + + @Override + public String toString() { + return "number of accounts " + data.getAccountList().size() + + ", number of items " + data.getItemList().size() + + ", number of reservations " + data.getReservationList().size() + + ", number of sales record " + data.getRecordList().size() + + ", number of ingredients " + data.getIngredientList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java b/src/main/java/seedu/restaurant/commons/events/storage/DataSavingExceptionEvent.java similarity index 77% rename from src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java rename to src/main/java/seedu/restaurant/commons/events/storage/DataSavingExceptionEvent.java index 7096107d8adf..5f98c95d113c 100644 --- a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java +++ b/src/main/java/seedu/restaurant/commons/events/storage/DataSavingExceptionEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.storage; +package seedu.restaurant.commons.events.storage; -import seedu.address.commons.events.BaseEvent; +import seedu.restaurant.commons.events.BaseEvent; /** * Indicates an exception during a file saving diff --git a/src/main/java/seedu/restaurant/commons/events/storage/UpdateAccountEvent.java b/src/main/java/seedu/restaurant/commons/events/storage/UpdateAccountEvent.java new file mode 100644 index 000000000000..a4a57252fba4 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/storage/UpdateAccountEvent.java @@ -0,0 +1,25 @@ +package seedu.restaurant.commons.events.storage; + +import seedu.restaurant.commons.core.session.UserSession; +import seedu.restaurant.commons.events.BaseEvent; +import seedu.restaurant.model.account.Account; +import seedu.restaurant.model.account.Username; + +//@@author AZhiKai +/** + * Indicates a user account has just been updated. + */ +public class UpdateAccountEvent extends BaseEvent { + + public final Username username; + + public UpdateAccountEvent(Account account) { + UserSession.update(account); + this.username = account.getUsername(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/ExitAppRequestEvent.java similarity index 68% rename from src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java rename to src/main/java/seedu/restaurant/commons/events/ui/ExitAppRequestEvent.java index a280bc7b5569..3cda1ae841e2 100644 --- a/src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java +++ b/src/main/java/seedu/restaurant/commons/events/ui/ExitAppRequestEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package seedu.restaurant.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.restaurant.commons.events.BaseEvent; /** * Indicates a request for App termination diff --git a/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/NewResultAvailableEvent.java similarity index 78% rename from src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java rename to src/main/java/seedu/restaurant/commons/events/ui/NewResultAvailableEvent.java index a5e8b2e13883..baa1f1e1d090 100644 --- a/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java +++ b/src/main/java/seedu/restaurant/commons/events/ui/NewResultAvailableEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package seedu.restaurant.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.restaurant.commons.events.BaseEvent; /** * Indicates that a new result is available. diff --git a/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/ShowHelpRequestEvent.java similarity index 68% rename from src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java rename to src/main/java/seedu/restaurant/commons/events/ui/ShowHelpRequestEvent.java index 87d5c70a1b79..e1d73782f50a 100644 --- a/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java +++ b/src/main/java/seedu/restaurant/commons/events/ui/ShowHelpRequestEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package seedu.restaurant.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.restaurant.commons.events.BaseEvent; /** * An event requesting to view the help page. diff --git a/src/main/java/seedu/restaurant/commons/events/ui/accounts/DisplayAccountListRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/accounts/DisplayAccountListRequestEvent.java new file mode 100644 index 000000000000..0052519e0bf0 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/accounts/DisplayAccountListRequestEvent.java @@ -0,0 +1,16 @@ +package seedu.restaurant.commons.events.ui.accounts; + +import seedu.restaurant.commons.events.BaseEvent; + +//@@author AZhiKai +/** + * An event requesting to display AccountListPanel. + */ +public class DisplayAccountListRequestEvent extends BaseEvent { + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/accounts/JumpToAccountListRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/accounts/JumpToAccountListRequestEvent.java new file mode 100644 index 000000000000..dcafd22d7612 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/accounts/JumpToAccountListRequestEvent.java @@ -0,0 +1,23 @@ +package seedu.restaurant.commons.events.ui.accounts; + +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.BaseEvent; + +//@@author AZhiKai + +/** + * Indicates a request to jump to the list of accounts + */ +public class JumpToAccountListRequestEvent extends BaseEvent { + + public final int targetIndex; + + public JumpToAccountListRequestEvent(Index targetIndex) { + this.targetIndex = targetIndex.getZeroBased(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/accounts/LoginEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/accounts/LoginEvent.java new file mode 100644 index 000000000000..88b756d111d1 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/accounts/LoginEvent.java @@ -0,0 +1,25 @@ +package seedu.restaurant.commons.events.ui.accounts; + +import seedu.restaurant.commons.core.session.UserSession; +import seedu.restaurant.commons.events.BaseEvent; +import seedu.restaurant.model.account.Account; +import seedu.restaurant.model.account.Username; + +//@@author AZhiKai +/** + * Indicates a user has just logged into the system. + */ +public class LoginEvent extends BaseEvent { + + public final Username username; + + public LoginEvent(Account account) { + UserSession.create(account); + this.username = account.getUsername(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/accounts/LogoutEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/accounts/LogoutEvent.java new file mode 100644 index 000000000000..c1160d36767f --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/accounts/LogoutEvent.java @@ -0,0 +1,20 @@ +package seedu.restaurant.commons.events.ui.accounts; + +import seedu.restaurant.commons.core.session.UserSession; +import seedu.restaurant.commons.events.BaseEvent; + +//@@author AZhiKai +/** + * Indicates a user has just logged out of system. + */ +public class LogoutEvent extends BaseEvent { + + public LogoutEvent() { + UserSession.destroy(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/ingredient/DisplayIngredientListRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/ingredient/DisplayIngredientListRequestEvent.java new file mode 100644 index 000000000000..74a623a2be8c --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/ingredient/DisplayIngredientListRequestEvent.java @@ -0,0 +1,15 @@ +package seedu.restaurant.commons.events.ui.ingredient; + +import seedu.restaurant.commons.events.BaseEvent; + +/** + * An event requesting to display IngredientListPanel. + */ +public class DisplayIngredientListRequestEvent extends BaseEvent { + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/ingredient/IngredientPanelSelectionChangedEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/ingredient/IngredientPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..7110960179a5 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/ingredient/IngredientPanelSelectionChangedEvent.java @@ -0,0 +1,26 @@ +package seedu.restaurant.commons.events.ui.ingredient; + +import seedu.restaurant.commons.events.BaseEvent; +import seedu.restaurant.model.ingredient.Ingredient; + +/** + * Represents a selection change in the Ingredient List Panel + */ +public class IngredientPanelSelectionChangedEvent extends BaseEvent { + + + private final Ingredient newSelection; + + public IngredientPanelSelectionChangedEvent(Ingredient newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public Ingredient getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/ingredient/JumpToIngredientListRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/ingredient/JumpToIngredientListRequestEvent.java new file mode 100644 index 000000000000..ace2df40f370 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/ingredient/JumpToIngredientListRequestEvent.java @@ -0,0 +1,23 @@ +package seedu.restaurant.commons.events.ui.ingredient; + +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.BaseEvent; + +//@@author rebstan97 +/** + * Indicates a request to jump to the list of ingredients + */ +public class JumpToIngredientListRequestEvent extends BaseEvent { + + public final int targetIndex; + + public JumpToIngredientListRequestEvent(Index targetIndex) { + this.targetIndex = targetIndex.getZeroBased(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/menu/DisplayItemListRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/menu/DisplayItemListRequestEvent.java new file mode 100644 index 000000000000..c54bdd88019c --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/menu/DisplayItemListRequestEvent.java @@ -0,0 +1,15 @@ +package seedu.restaurant.commons.events.ui.menu; + +import seedu.restaurant.commons.events.BaseEvent; + +//@@author yican95 +/** + * An event requesting to display ItemListPanel. + */ +public class DisplayItemListRequestEvent extends BaseEvent { + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/menu/ItemPanelSelectionChangedEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/menu/ItemPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..4fc05b00dc15 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/menu/ItemPanelSelectionChangedEvent.java @@ -0,0 +1,26 @@ +package seedu.restaurant.commons.events.ui.menu; + +import seedu.restaurant.commons.events.BaseEvent; +import seedu.restaurant.model.menu.Item; + +//@@author yican95 +/** + * Represents a selection change in the Item List Panel + */ +public class ItemPanelSelectionChangedEvent extends BaseEvent { + + private final Item newSelection; + + public ItemPanelSelectionChangedEvent(Item newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public Item getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/menu/JumpToItemListRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/menu/JumpToItemListRequestEvent.java new file mode 100644 index 000000000000..616972b210cb --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/menu/JumpToItemListRequestEvent.java @@ -0,0 +1,23 @@ +package seedu.restaurant.commons.events.ui.menu; + +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.BaseEvent; + +//@@author yican95 +/** + * Indicates a request to jump to the list of items + */ +public class JumpToItemListRequestEvent extends BaseEvent { + + public final int targetIndex; + + public JumpToItemListRequestEvent(Index targetIndex) { + this.targetIndex = targetIndex.getZeroBased(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/reservation/DisplayReservationListRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/reservation/DisplayReservationListRequestEvent.java new file mode 100644 index 000000000000..9759432ee79b --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/reservation/DisplayReservationListRequestEvent.java @@ -0,0 +1,16 @@ +package seedu.restaurant.commons.events.ui.reservation; + +import seedu.restaurant.commons.events.BaseEvent; + +//@@author m4dkip +/** + * An event requesting to display ReservationListPanel. + */ +public class DisplayReservationListRequestEvent extends BaseEvent { + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/reservation/JumpToReservationListRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/reservation/JumpToReservationListRequestEvent.java new file mode 100644 index 000000000000..605a6b70ef19 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/reservation/JumpToReservationListRequestEvent.java @@ -0,0 +1,23 @@ +package seedu.restaurant.commons.events.ui.reservation; + +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.BaseEvent; + +//@@author m4dkip +/** + * Indicates a request to jump to the list of reservations + */ +public class JumpToReservationListRequestEvent extends BaseEvent { + + public final int targetIndex; + + public JumpToReservationListRequestEvent(Index targetIndex) { + this.targetIndex = targetIndex.getZeroBased(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/reservation/ReservationPanelSelectionChangedEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/reservation/ReservationPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..387e180e26b2 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/reservation/ReservationPanelSelectionChangedEvent.java @@ -0,0 +1,27 @@ +package seedu.restaurant.commons.events.ui.reservation; + +import seedu.restaurant.commons.events.BaseEvent; +import seedu.restaurant.model.reservation.Reservation; + +//@@author m4dkip +/** + * Represents a selection change in the Reservation List Panel + */ +public class ReservationPanelSelectionChangedEvent extends BaseEvent { + + + private final Reservation newSelection; + + public ReservationPanelSelectionChangedEvent(Reservation newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public Reservation getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplayRankingEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplayRankingEvent.java new file mode 100644 index 000000000000..5f48b7f61357 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplayRankingEvent.java @@ -0,0 +1,27 @@ +package seedu.restaurant.commons.events.ui.sales; + +import java.util.Map; + +import seedu.restaurant.commons.events.BaseEvent; + +//@@author HyperionNKJ +/** + * An event requesting to display the ranking given in the Map + */ +public class DisplayRankingEvent extends BaseEvent { + + private Map rankingToDisplay; + + public DisplayRankingEvent(Map rankingToDisplay) { + this.rankingToDisplay = rankingToDisplay; + } + + public Map getRankingToDisplay() { + return rankingToDisplay; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplayRecordListRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplayRecordListRequestEvent.java new file mode 100644 index 000000000000..26f99614edc1 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplayRecordListRequestEvent.java @@ -0,0 +1,15 @@ +package seedu.restaurant.commons.events.ui.sales; + +import seedu.restaurant.commons.events.BaseEvent; + +//@@author HyperionNKJ +/** + * An event requesting to display RecordListPanel. + */ +public class DisplayRecordListRequestEvent extends BaseEvent { + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplaySalesChartEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplaySalesChartEvent.java new file mode 100644 index 000000000000..72b94cf7f5b3 --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplaySalesChartEvent.java @@ -0,0 +1,28 @@ +package seedu.restaurant.commons.events.ui.sales; + +import java.util.Map; + +import seedu.restaurant.commons.events.BaseEvent; +import seedu.restaurant.model.sales.Date; + +//@@author HyperionNKJ +/** + * An event requesting to display the sales line chart using data given in the Map + */ +public class DisplaySalesChartEvent extends BaseEvent { + + private Map salesData; + + public DisplaySalesChartEvent(Map salesData) { + this.salesData = salesData; + } + + public Map getSalesData() { + return salesData; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplaySalesReportEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplaySalesReportEvent.java new file mode 100644 index 000000000000..f87e88fe689b --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/sales/DisplaySalesReportEvent.java @@ -0,0 +1,26 @@ +package seedu.restaurant.commons.events.ui.sales; + +import seedu.restaurant.commons.events.BaseEvent; +import seedu.restaurant.model.sales.SalesReport; + +//@@author HyperionNKJ +/** + * An event requesting to display the sales report of a specified date + */ +public class DisplaySalesReportEvent extends BaseEvent { + + private SalesReport salesReportToDisplay; + + public DisplaySalesReportEvent(SalesReport salesReportToDisplay) { + this.salesReportToDisplay = salesReportToDisplay; + } + + public SalesReport getSalesReportToDisplay() { + return salesReportToDisplay; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/sales/JumpToRecordListRequestEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/sales/JumpToRecordListRequestEvent.java new file mode 100644 index 000000000000..57e9e656282f --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/sales/JumpToRecordListRequestEvent.java @@ -0,0 +1,22 @@ +package seedu.restaurant.commons.events.ui.sales; + +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.BaseEvent; + +//@@author HyperionNKJ +/** + * Indicates a request to jump to the list of sales records + */ +public class JumpToRecordListRequestEvent extends BaseEvent { + + public final int targetIndex; + + public JumpToRecordListRequestEvent(Index targetIndex) { + this.targetIndex = targetIndex.getZeroBased(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/restaurant/commons/events/ui/sales/RecordPanelSelectionChangedEvent.java b/src/main/java/seedu/restaurant/commons/events/ui/sales/RecordPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..c1e66fb824bc --- /dev/null +++ b/src/main/java/seedu/restaurant/commons/events/ui/sales/RecordPanelSelectionChangedEvent.java @@ -0,0 +1,29 @@ +package seedu.restaurant.commons.events.ui.sales; + +import seedu.restaurant.commons.events.BaseEvent; +import seedu.restaurant.model.sales.SalesRecord; + +//@@author HyperionNKJ +/** + * Represents a selection change in the Record List Panel + */ +public class RecordPanelSelectionChangedEvent extends BaseEvent { + + private final SalesRecord newSelection; + + public RecordPanelSelectionChangedEvent(SalesRecord newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + /** + * Returns newSelection in this changed event + */ + public SalesRecord getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/seedu/restaurant/commons/exceptions/DataConversionException.java similarity index 83% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/seedu/restaurant/commons/exceptions/DataConversionException.java index 1f689bd8e3f9..aeed01390e65 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/seedu/restaurant/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.restaurant.commons.exceptions; /** * Represents an error during conversion of data from one format to another diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/restaurant/commons/exceptions/IllegalValueException.java similarity index 92% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/seedu/restaurant/commons/exceptions/IllegalValueException.java index 19124db485c9..091c16f6487e 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/restaurant/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.restaurant.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/restaurant/commons/util/AppUtil.java similarity index 93% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/seedu/restaurant/commons/util/AppUtil.java index da90201dfd64..f04d5cf77c68 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/restaurant/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package seedu.restaurant.commons.util; import static java.util.Objects.requireNonNull; import javafx.scene.image.Image; -import seedu.address.MainApp; +import seedu.restaurant.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/restaurant/commons/util/CollectionUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/seedu/restaurant/commons/util/CollectionUtil.java index eafe4dfd6818..3f556c547cf6 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/restaurant/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.restaurant.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/seedu/restaurant/commons/util/ConfigUtil.java similarity index 76% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/seedu/restaurant/commons/util/ConfigUtil.java index f7f8a2bd44c0..7e449f0e3a27 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/seedu/restaurant/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package seedu.restaurant.commons.util; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataConversionException; +import seedu.restaurant.commons.core.Config; +import seedu.restaurant.commons.exceptions.DataConversionException; /** * A class for accessing the Config File. diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/restaurant/commons/util/FileUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/seedu/restaurant/commons/util/FileUtil.java index b1e2767cdd92..4b71a0de3605 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/restaurant/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.restaurant.commons.util; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/restaurant/commons/util/JsonUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/seedu/restaurant/commons/util/JsonUtil.java index 8ef609f055df..1c425e108e2f 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/restaurant/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.restaurant.commons.util; import static java.util.Objects.requireNonNull; @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +import seedu.restaurant.commons.core.LogsCenter; +import seedu.restaurant.commons.exceptions.DataConversionException; /** * Converts a Java object instance to JSON and vice versa diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/restaurant/commons/util/StringUtil.java similarity index 51% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/seedu/restaurant/commons/util/StringUtil.java index 61cc8c9a1cb8..efe48bb11df2 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/restaurant/commons/util/StringUtil.java @@ -1,7 +1,8 @@ -package seedu.address.commons.util; +package seedu.restaurant.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.restaurant.commons.util.AppUtil.checkArgument; +import static seedu.restaurant.commons.util.CollectionUtil.requireAllNonNull; import java.io.PrintWriter; import java.io.StringWriter; @@ -13,13 +14,13 @@ public class StringUtil { /** - * Returns true if the {@code sentence} contains the {@code word}. - * Ignores case, but a full word match is required. - *
examples:
+     * Returns true if the {@code sentence} contains the {@code word}. Ignores case, but a full word match is required.
+     * 
examples:
      *       containsWordIgnoreCase("ABc def", "abc") == true
      *       containsWordIgnoreCase("ABc def", "DEF") == true
      *       containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
      *       
+ * * @param sentence cannot be null * @param word cannot be null, cannot be empty, must be a single word */ @@ -38,6 +39,31 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Returns true if the {@code sentence} contains the {@code substring} with case ignored. + *
examples:
+     *       containsSubstringIgnoreCase("azhikai", "zhikai") == true
+     *       containsSubstringIgnoreCase("azhikai", "KAI") == true
+     *       containsSubstringIgnoreCase("root", "zhikai") == false //does not contain the substring
+     *       
+ * + * @param sentence cannot be null + * @param substring cannot be null, cannot be empty, must be a single word + */ + public static boolean containsSubstringIgnoreCase(String sentence, String substring) { + requireAllNonNull(sentence, substring); + + String preppedSubstring = substring.trim().toLowerCase(); + + checkArgument(!preppedSubstring.isEmpty(), "Substring parameter cannot be empty"); + checkArgument(preppedSubstring.split("\\s+").length == 1, "Substring parameter " + + "should be a single word"); + + String preppedSentence = sentence.trim().toLowerCase(); + + return preppedSentence.contains(preppedSubstring); + } + /** * Returns a detailed message of the t, including the stack trace. */ @@ -53,6 +79,7 @@ public static String getDetails(Throwable t) { * e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE}
* Will return false for any other non-null string input * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * * @throws NullPointerException if {@code s} is null. */ public static boolean isNonZeroUnsignedInteger(String s) { @@ -65,4 +92,25 @@ public static boolean isNonZeroUnsignedInteger(String s) { return false; } } + + //@@author rebstan97 + /** + * Returns true if {@code s} represents an unsigned integer inclusive of zero + * e.g. 0, 1, 2, 3, ..., {@code Integer.MAX_VALUE}
+ * Will return false for any other non-null string input + * e.g. empty string, "-1", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * + * @throws NullPointerException if {@code s} is null. + */ + public static boolean isUnsignedInteger(String s) { + requireNonNull(s); + + try { + int value = Integer.parseInt(s); + return value >= 0 && !s.startsWith("+"); // "+1" is successfully parsed by Integer#parseInt(String) + } catch (NumberFormatException nfe) { + return false; + } + } + } diff --git a/src/main/java/seedu/address/commons/util/XmlUtil.java b/src/main/java/seedu/restaurant/commons/util/XmlUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/XmlUtil.java rename to src/main/java/seedu/restaurant/commons/util/XmlUtil.java index a78cd15b7f0c..48237f90d7e7 100644 --- a/src/main/java/seedu/address/commons/util/XmlUtil.java +++ b/src/main/java/seedu/restaurant/commons/util/XmlUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.restaurant.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/logic/CommandHistory.java b/src/main/java/seedu/restaurant/logic/CommandHistory.java similarity index 88% rename from src/main/java/seedu/address/logic/CommandHistory.java rename to src/main/java/seedu/restaurant/logic/CommandHistory.java index 39bca9b8df57..4635c9bf42aa 100644 --- a/src/main/java/seedu/address/logic/CommandHistory.java +++ b/src/main/java/seedu/restaurant/logic/CommandHistory.java @@ -1,4 +1,4 @@ -package seedu.address.logic; +package seedu.restaurant.logic; import static java.util.Objects.requireNonNull; @@ -9,6 +9,7 @@ * Stores the history of commands executed. */ public class CommandHistory { + private LinkedList userInputHistory; public CommandHistory() { @@ -27,6 +28,14 @@ public void add(String userInput) { userInputHistory.add(userInput); } + /** + * Clears the list. + */ + public void clear() { + userInputHistory.clear(); + assert userInputHistory.size() == 0; + } + /** * Returns a defensive copy of {@code userInputHistory}. */ diff --git a/src/main/java/seedu/address/logic/ListElementPointer.java b/src/main/java/seedu/restaurant/logic/ListElementPointer.java similarity index 99% rename from src/main/java/seedu/address/logic/ListElementPointer.java rename to src/main/java/seedu/restaurant/logic/ListElementPointer.java index ca4085d98a11..18cb262acd18 100644 --- a/src/main/java/seedu/address/logic/ListElementPointer.java +++ b/src/main/java/seedu/restaurant/logic/ListElementPointer.java @@ -1,4 +1,4 @@ -package seedu.address.logic; +package seedu.restaurant.logic; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/seedu/restaurant/logic/Logic.java b/src/main/java/seedu/restaurant/logic/Logic.java new file mode 100644 index 000000000000..7c7864c7f834 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/Logic.java @@ -0,0 +1,43 @@ +package seedu.restaurant.logic; + +import javafx.collections.ObservableList; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.logic.parser.exceptions.ParseException; +import seedu.restaurant.model.account.Account; +import seedu.restaurant.model.ingredient.Ingredient; +import seedu.restaurant.model.menu.Item; +import seedu.restaurant.model.reservation.Reservation; +import seedu.restaurant.model.sales.SalesRecord; + +/** + * API of the Logic component + */ +public interface Logic { + /** + * Executes the command and returns the result. + * @param commandText The command as entered by the user. + * @return the result of the command execution. + * @throws CommandException If an error occurs during command execution. + * @throws ParseException If an error occurs during parsing. + */ + CommandResult execute(String commandText) throws CommandException, ParseException; + + /** Returns an unmodifiable view of the filtered list of account */ + ObservableList getFilteredAccountList(); + + /** Returns an unmodifiable view of the filtered list of ingredients */ + ObservableList getFilteredIngredientList(); + + /** Returns an unmodifiable view of the filtered list of items */ + ObservableList getFilteredItemList(); + + /** Returns an unmodifiable view of the filtered list of reservations */ + ObservableList getFilteredReservationList(); + + /** Returns an unmodifiable view of the filtered list of sales records */ + ObservableList getFilteredRecordList(); + + /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ + ListElementPointer getHistorySnapshot(); +} diff --git a/src/main/java/seedu/restaurant/logic/LogicManager.java b/src/main/java/seedu/restaurant/logic/LogicManager.java new file mode 100644 index 000000000000..adbebb00f22a --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/LogicManager.java @@ -0,0 +1,118 @@ +package seedu.restaurant.logic; + +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_PASSWORD; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import seedu.restaurant.commons.core.ComponentManager; +import seedu.restaurant.commons.core.LogsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.session.UserSession; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.ExitCommand; +import seedu.restaurant.logic.commands.HelpCommand; +import seedu.restaurant.logic.commands.account.LoginCommand; +import seedu.restaurant.logic.commands.account.LogoutCommand; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.logic.commands.menu.FilterMenuCommand; +import seedu.restaurant.logic.commands.menu.FindItemCommand; +import seedu.restaurant.logic.commands.menu.ListItemsCommand; +import seedu.restaurant.logic.commands.menu.SelectItemCommand; +import seedu.restaurant.logic.commands.menu.TodaySpecialCommand; +import seedu.restaurant.logic.parser.RestaurantBookParser; +import seedu.restaurant.logic.parser.exceptions.ParseException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.account.Account; +import seedu.restaurant.model.account.Password; +import seedu.restaurant.model.ingredient.Ingredient; +import seedu.restaurant.model.menu.Item; +import seedu.restaurant.model.reservation.Reservation; +import seedu.restaurant.model.sales.SalesRecord; + +/** + * The main LogicManager of the app. + */ +public class LogicManager extends ComponentManager implements Logic { + + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + + private final Model model; + private final CommandHistory history; + private final RestaurantBookParser restaurantBookParser; + + public LogicManager(Model model) { + this.model = model; + history = new CommandHistory(); + restaurantBookParser = new RestaurantBookParser(); + } + + //@@author AZhiKai2 + + /** + * Verifies if a {@code Command} is a guest command which can be executed without being authenticated. + */ + private boolean isGuestCommand(Command command) { + return command instanceof LoginCommand || command instanceof HelpCommand + || command instanceof ExitCommand || command instanceof SelectItemCommand + || command instanceof FindItemCommand || command instanceof FilterMenuCommand + || command instanceof ListItemsCommand || command instanceof TodaySpecialCommand; + } + + @Override + public CommandResult execute(String commandText) throws CommandException, ParseException { + String commandTextToLog = commandText; + if (commandText.contains(PREFIX_PASSWORD.getPrefix())) { + commandTextToLog = Password.maskPassword(commandText); + } + logger.info("----------------[USER COMMAND][" + commandTextToLog + "]----------------"); + + Command command = null; + try { + command = restaurantBookParser.parseCommand(commandText); + + if (!isGuestCommand(command) && !UserSession.isAuthenticated()) { + throw new CommandException(Messages.MESSAGE_COMMAND_FORBIDDEN); + } + + return command.execute(model, history); + } finally { + if (command instanceof LogoutCommand) { + history.clear(); + } else { + history.add(commandText); + } + } + } + + @Override + public ObservableList getFilteredAccountList() { + return model.getFilteredAccountList(); + } + + @Override + public ObservableList getFilteredIngredientList() { + return model.getFilteredIngredientList(); + } + + @Override + public ObservableList getFilteredItemList() { + return model.getFilteredItemList(); + } + + @Override + public ObservableList getFilteredReservationList() { + return model.getFilteredReservationList(); + } + + @Override + public ObservableList getFilteredRecordList() { + return model.getFilteredRecordList(); + } + + @Override + public ListElementPointer getHistorySnapshot() { + return new ListElementPointer(history.getHistory()); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/ClearCommand.java b/src/main/java/seedu/restaurant/logic/commands/ClearCommand.java new file mode 100644 index 000000000000..4f3cb8786deb --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ClearCommand.java @@ -0,0 +1,26 @@ +package seedu.restaurant.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.util.SampleDataUtil; + +/** + * Clears the restaurant book. + */ +public class ClearCommand extends Command { + + public static final String COMMAND_WORD = "clear"; + public static final String COMMAND_ALIAS = "c"; + public static final String MESSAGE_SUCCESS = "Restaurant Book has been cleared"; + + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.resetData(SampleDataUtil.getDefaultRestaurantBook()); + model.commitRestaurantBook(); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/restaurant/logic/commands/Command.java similarity index 76% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/seedu/restaurant/logic/commands/Command.java index 34e99d786ec6..cd4c56c1a76e 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/restaurant/logic/commands/Command.java @@ -1,8 +1,8 @@ -package seedu.address.logic.commands; +package seedu.restaurant.logic.commands; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/restaurant/logic/commands/CommandResult.java similarity index 87% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/seedu/restaurant/logic/commands/CommandResult.java index abdc267a2c44..95a44ed08705 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/restaurant/logic/commands/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.restaurant.logic.commands; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/restaurant/logic/commands/ExitCommand.java similarity index 56% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/seedu/restaurant/logic/commands/ExitCommand.java index e848fa918964..1157d232feb5 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/restaurant/logic/commands/ExitCommand.java @@ -1,9 +1,9 @@ -package seedu.address.logic.commands; +package seedu.restaurant.logic.commands; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.ExitAppRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.model.Model; /** * Terminates the program. @@ -11,8 +11,9 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; + public static final String COMMAND_ALIAS = "ex"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Restaurant Book as requested ..."; @Override public CommandResult execute(Model model, CommandHistory history) { diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/restaurant/logic/commands/HelpCommand.java similarity index 66% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/seedu/restaurant/logic/commands/HelpCommand.java index 66305e95d8f2..1171c5b708bc 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/restaurant/logic/commands/HelpCommand.java @@ -1,9 +1,9 @@ -package seedu.address.logic.commands; +package seedu.restaurant.logic.commands; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.ui.ShowHelpRequestEvent; -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.ShowHelpRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.model.Model; /** * Format full help instructions for every command for display. @@ -11,11 +11,12 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; + public static final String COMMAND_ALIAS = "h"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + "Example: " + COMMAND_WORD; - public static final String SHOWING_HELP_MESSAGE = "Opened help window."; + public static final String SHOWING_HELP_MESSAGE = "Opened help window"; @Override public CommandResult execute(Model model, CommandHistory history) { diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/restaurant/logic/commands/HistoryCommand.java similarity index 81% rename from src/main/java/seedu/address/logic/commands/HistoryCommand.java rename to src/main/java/seedu/restaurant/logic/commands/HistoryCommand.java index f1541fb57f20..089877ebeb84 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/restaurant/logic/commands/HistoryCommand.java @@ -1,12 +1,12 @@ -package seedu.address.logic.commands; +package seedu.restaurant.logic.commands; import static java.util.Objects.requireNonNull; import java.util.Collections; import java.util.List; -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.model.Model; /** * Lists all the commands entered by user from the start of app launch. @@ -14,8 +14,10 @@ public class HistoryCommand extends Command { public static final String COMMAND_WORD = "history"; + public static final String COMMAND_ALIAS = "hist"; + public static final String MESSAGE_SUCCESS = "Entered commands (from most recent to earliest):\n%1$s"; - public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; + public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands"; @Override public CommandResult execute(Model model, CommandHistory history) { diff --git a/src/main/java/seedu/restaurant/logic/commands/RedoCommand.java b/src/main/java/seedu/restaurant/logic/commands/RedoCommand.java new file mode 100644 index 000000000000..eaeef68dc2da --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/RedoCommand.java @@ -0,0 +1,38 @@ +package seedu.restaurant.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ACCOUNTS; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ITEMS; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_RECORDS; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_RESERVATIONS; + +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; + +/** + * Reverts the {@code model}'s restaurant book to its previously undone state. + */ +public class RedoCommand extends Command { + + public static final String COMMAND_WORD = "redo"; + public static final String COMMAND_ALIAS = "r"; + public static final String MESSAGE_SUCCESS = "Redo success!"; + public static final String MESSAGE_FAILURE = "No more commands to redo!"; + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.canRedoRestaurantBook()) { + throw new CommandException(MESSAGE_FAILURE); + } + + model.redoRestaurantBook(); + model.updateFilteredItemList(PREDICATE_SHOW_ALL_ITEMS); + model.updateFilteredAccountList(PREDICATE_SHOW_ALL_ACCOUNTS); + model.updateFilteredRecordList(PREDICATE_SHOW_ALL_RECORDS); + model.updateFilteredReservationList(PREDICATE_SHOW_ALL_RESERVATIONS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/UndoCommand.java b/src/main/java/seedu/restaurant/logic/commands/UndoCommand.java new file mode 100644 index 000000000000..6fc9a9c26983 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/UndoCommand.java @@ -0,0 +1,40 @@ +package seedu.restaurant.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ACCOUNTS; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_INGREDIENTS; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ITEMS; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_RECORDS; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_RESERVATIONS; + +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; + +/** + * Reverts the {@code model}'s restaurant book to its previous state. + */ +public class UndoCommand extends Command { + + public static final String COMMAND_WORD = "undo"; + public static final String COMMAND_ALIAS = "u"; + public static final String MESSAGE_SUCCESS = "Undo success!"; + public static final String MESSAGE_FAILURE = "No more commands to undo!"; + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.canUndoRestaurantBook()) { + throw new CommandException(MESSAGE_FAILURE); + } + + model.undoRestaurantBook(); + model.updateFilteredIngredientList(PREDICATE_SHOW_ALL_INGREDIENTS); + model.updateFilteredItemList(PREDICATE_SHOW_ALL_ITEMS); + model.updateFilteredAccountList(PREDICATE_SHOW_ALL_ACCOUNTS); + model.updateFilteredRecordList(PREDICATE_SHOW_ALL_RECORDS); + model.updateFilteredReservationList(PREDICATE_SHOW_ALL_RESERVATIONS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/account/ChangePasswordCommand.java b/src/main/java/seedu/restaurant/logic/commands/account/ChangePasswordCommand.java new file mode 100644 index 000000000000..8688fc3b821c --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/account/ChangePasswordCommand.java @@ -0,0 +1,134 @@ +package seedu.restaurant.logic.commands.account; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_NEW_PASSWORD; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ACCOUNTS; + +import java.util.Optional; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.session.UserSession; +import seedu.restaurant.commons.events.storage.UpdateAccountEvent; +import seedu.restaurant.commons.util.CollectionUtil; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.account.Account; +import seedu.restaurant.model.account.Password; + +//@@author AZhiKai +/** + * Change the password of an existing {@code Account}. + */ +public class ChangePasswordCommand extends Command { + + public static final String COMMAND_WORD = "change-password"; + public static final String COMMAND_ALIAS = "cp"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Change the password of an existing account. " + + "Parameters: " + + PREFIX_NEW_PASSWORD + "NEW_PASSWORD\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NEW_PASSWORD + "1122qq"; + + public static final String MESSAGE_SUCCESS = "Successfully updated the account %s"; + + private final EditAccountDescriptor editAccountDescriptor; + + /** + * @param editAccountDescriptor details to edit the account with. + */ + public ChangePasswordCommand(EditAccountDescriptor editAccountDescriptor) { + requireNonNull(editAccountDescriptor); + + this.editAccountDescriptor = new EditAccountDescriptor(editAccountDescriptor); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + // Session guarantees to have been set, thus an account exists in the session + Account accountToEdit = UserSession.getAccount(); + Account editedAccount = createEditedAccount(accountToEdit, editAccountDescriptor); + + model.updateAccount(accountToEdit, editedAccount); + model.updateFilteredAccountList(PREDICATE_SHOW_ALL_ACCOUNTS); + model.commitRestaurantBook(); + + EventsCenter.getInstance().post(new UpdateAccountEvent(editedAccount)); + return new CommandResult(String.format(MESSAGE_SUCCESS, editedAccount)); + } + + /** + * Creates and returns a {@code Account} with the details of {@code accountToEdit} edited with {@code + * editAccountDescriptor}. + */ + private static Account createEditedAccount(Account accountToEdit, EditAccountDescriptor editAccountDescriptor) { + assert accountToEdit != null; + + Password updatedPassword = editAccountDescriptor.getPassword().orElse(accountToEdit.getPassword()); + + return new Account(accountToEdit.getUsername(), updatedPassword, accountToEdit.getName()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ChangePasswordCommand + && editAccountDescriptor.equals(((ChangePasswordCommand) other).editAccountDescriptor)); + } + + /** + * Stores the details to edit the account with. Each non-empty field value will replace the corresponding field + * value of the account. + */ + public static class EditAccountDescriptor { + + private Password password; + + public EditAccountDescriptor() {} + + /** + * Copy constructor. + */ + public EditAccountDescriptor(EditAccountDescriptor toCopy) { + setPassword(toCopy.password); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(password); + } + + public void setPassword(Password password) { + this.password = password; + } + + public Optional getPassword() { + return Optional.ofNullable(password); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditAccountDescriptor)) { + return false; + } + + // state check + EditAccountDescriptor e = (EditAccountDescriptor) other; + + return password.equals(e.password); + } + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/account/DeregisterCommand.java b/src/main/java/seedu/restaurant/logic/commands/account/DeregisterCommand.java new file mode 100644 index 000000000000..1b589d4392c3 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/account/DeregisterCommand.java @@ -0,0 +1,69 @@ +package seedu.restaurant.logic.commands.account; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_ID; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.session.UserSession; +import seedu.restaurant.commons.events.ui.accounts.DisplayAccountListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.account.Account; + +//@@author AZhiKai +/** + * Deregisters an existing {@code Account}. This command should only be executed by a user with high privileges + * when roles are implemented in v2.0. + */ +public class DeregisterCommand extends Command { + + public static final String COMMAND_WORD = "deregister"; + public static final String COMMAND_ALIAS = "dereg"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deregister an existing account. " + + "Parameters: " + + PREFIX_ID + "USERNAME\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_ID + "azhikai"; + + public static final String MESSAGE_SUCCESS = "Account deregistered: %1$s"; + public static final String MESSAGE_USERNAME_NOT_FOUND = "This username does not exist"; + public static final String MESSAGE_CANNOT_SELF_DEREGISTER = "You cannot deregister your own account"; + + private final Account account; + + public DeregisterCommand(Account account) { + requireNonNull(account); + this.account = account; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!model.hasAccount(account)) { + throw new CommandException(MESSAGE_USERNAME_NOT_FOUND); + } + + Account retrievedAccount = model.getAccount(account); + + if (UserSession.getAccount().equals(retrievedAccount)) { + throw new CommandException(MESSAGE_CANNOT_SELF_DEREGISTER); + } + + model.removeAccount(retrievedAccount); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayAccountListRequestEvent()); + return new CommandResult(String.format(MESSAGE_SUCCESS, account)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeregisterCommand // instanceof handles nulls + && account.getUsername().equals(((DeregisterCommand) other).account.getUsername())); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/account/FindAccountCommand.java b/src/main/java/seedu/restaurant/logic/commands/account/FindAccountCommand.java new file mode 100644 index 000000000000..a47c2ab59c50 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/account/FindAccountCommand.java @@ -0,0 +1,58 @@ +package seedu.restaurant.logic.commands.account; + +import static java.util.Objects.requireNonNull; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.accounts.DisplayAccountListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.account.UsernameContainsKeywordPredicate; + +//@@author AZhiKai + +/** + * Finds and lists all accounts whose username contains any of the argument keywords. Keyword matching is case + * insensitive. + */ +public class FindAccountCommand extends Command { + + public static final String COMMAND_WORD = "find-account"; + + public static final String COMMAND_ALIAS = "fa"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Find all accounts whose username contains " + + "the specified keyword (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD \n" + + "Example: " + COMMAND_WORD + " root"; + + public static final String MESSAGE_SUCCESS = "Listed %d accounts"; + public static final String MESSAGE_ONE_KEYWORD_ONLY = "Multiple keywords are not allowed"; + + private final UsernameContainsKeywordPredicate predicate; + + public FindAccountCommand(UsernameContainsKeywordPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + try { + model.updateFilteredAccountList(predicate); + } catch (IllegalArgumentException ex) { + throw new CommandException(MESSAGE_ONE_KEYWORD_ONLY); + } + EventsCenter.getInstance().post(new DisplayAccountListRequestEvent()); + return new CommandResult(String.format(MESSAGE_SUCCESS, model.getFilteredAccountList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindAccountCommand // instanceof handles nulls + && predicate.equals(((FindAccountCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/account/ListAccountsCommand.java b/src/main/java/seedu/restaurant/logic/commands/account/ListAccountsCommand.java new file mode 100644 index 000000000000..05437937dcf1 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/account/ListAccountsCommand.java @@ -0,0 +1,33 @@ +package seedu.restaurant.logic.commands.account; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ACCOUNTS; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.accounts.DisplayAccountListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.model.Model; + +//@@author AZhiKai +/** + * List all {@code Account}. + */ +public class ListAccountsCommand extends Command { + + public static final String COMMAND_WORD = "list-accounts"; + + public static final String COMMAND_ALIAS = "la"; + + public static final String MESSAGE_SUCCESS = "Listed all %d accounts"; + + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.updateFilteredAccountList(PREDICATE_SHOW_ALL_ACCOUNTS); + EventsCenter.getInstance().post(new DisplayAccountListRequestEvent()); + return new CommandResult(String.format(MESSAGE_SUCCESS, model.getFilteredAccountList().size())); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/account/LoginCommand.java b/src/main/java/seedu/restaurant/logic/commands/account/LoginCommand.java new file mode 100644 index 000000000000..13591f15818c --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/account/LoginCommand.java @@ -0,0 +1,77 @@ +package seedu.restaurant.logic.commands.account; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_ID; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_PASSWORD; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.session.UserSession; +import seedu.restaurant.commons.events.ui.accounts.LoginEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.account.Account; +import seedu.restaurant.model.account.Password; + +//@@author AZhiKai +/** + * Logs the user into an existing {@code Account}, and create a {@code UserSession}. + */ +public class LoginCommand extends Command { + + public static final String COMMAND_WORD = "login"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Login to the system with an existing account. " + + "Parameters: " + + PREFIX_ID + "USERNAME " + + PREFIX_PASSWORD + "PASSWORD\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_ID + "azhikai " + + PREFIX_PASSWORD + "1122qq"; + + public static final String MESSAGE_SUCCESS = "Successfully logged in to %s"; + public static final String MESSAGE_ACCOUNT_NOT_FOUND = "The account does not exist"; + public static final String MESSAGE_WRONG_PASSWORD = "The credential is invalid"; + public static final String MESSAGE_ALREADY_AUTHENTICATED = "You are already logged in"; + + private final Account toLogin; + + public LoginCommand(Account toLogin) { + requireNonNull(toLogin); + this.toLogin = toLogin; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (UserSession.isAuthenticated()) { + throw new CommandException(MESSAGE_ALREADY_AUTHENTICATED); + } + + if (!model.hasAccount(toLogin)) { + throw new CommandException(MESSAGE_ACCOUNT_NOT_FOUND); + } + + Account retrievedAccount = model.getAccount(toLogin); + + boolean isVerified = Password.verifyPassword(toLogin.getPassword().toString(), + retrievedAccount.getPassword().toString().getBytes()); + + if (!isVerified) { + throw new CommandException(MESSAGE_WRONG_PASSWORD); + } + + EventsCenter.getInstance().post(new LoginEvent(retrievedAccount)); + return new CommandResult(String.format(MESSAGE_SUCCESS, retrievedAccount)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LoginCommand // instanceof handles nulls + && toLogin.equals(((LoginCommand) other).toLogin)); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/account/LogoutCommand.java b/src/main/java/seedu/restaurant/logic/commands/account/LogoutCommand.java new file mode 100644 index 000000000000..2532f26e2af4 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/account/LogoutCommand.java @@ -0,0 +1,40 @@ +package seedu.restaurant.logic.commands.account; + +import static java.util.Objects.requireNonNull; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.session.UserSession; +import seedu.restaurant.commons.events.ui.accounts.LogoutEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; + +//@@author AZhiKai +/** + * Logs the user out, and destroy the {@code UserSession}. + */ +public class LogoutCommand extends Command { + + public static final String COMMAND_WORD = "logout"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Logout of the system. " + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_SUCCESS = "You have been logged out"; + public static final String MESSAGE_NOT_AUTHENTICATED = "You are not logged in"; + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (!UserSession.isAuthenticated()) { + throw new CommandException(MESSAGE_NOT_AUTHENTICATED); + } + + model.resetRestaurantBookVersion(); + EventsCenter.getInstance().post(new LogoutEvent()); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/account/RegisterCommand.java b/src/main/java/seedu/restaurant/logic/commands/account/RegisterCommand.java new file mode 100644 index 000000000000..495ceca4100e --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/account/RegisterCommand.java @@ -0,0 +1,67 @@ +package seedu.restaurant.logic.commands.account; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_ID; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_NAME; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_PASSWORD; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.accounts.DisplayAccountListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.account.Account; + +//@@author AZhiKai +/** + * Adds a new {@code Account}. + */ +public class RegisterCommand extends Command { + + public static final String COMMAND_WORD = "register"; + public static final String COMMAND_ALIAS = "reg"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Registers a new user account. " + + "Parameters: " + + PREFIX_ID + "USERNAME " + + PREFIX_PASSWORD + "PASSWORD " + + PREFIX_NAME + "NAME\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_ID + "azhikai " + + PREFIX_PASSWORD + "1122qq " + + PREFIX_NAME + "Ang Zhi Kai"; + + public static final String MESSAGE_SUCCESS = "New account registered: %1$s"; + public static final String MESSAGE_DUPLICATE_USERNAME = "This username already exists. Please choose another " + + "username"; + + private final Account account; + + public RegisterCommand(Account account) { + requireNonNull(account); + this.account = account; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.hasAccount(account)) { + throw new CommandException(MESSAGE_DUPLICATE_USERNAME); + } + + model.addAccount(account); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayAccountListRequestEvent()); + return new CommandResult(String.format(MESSAGE_SUCCESS, account)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RegisterCommand // instanceof handles nulls + && account.equals(((RegisterCommand) other).account)); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/account/SelectAccountCommand.java b/src/main/java/seedu/restaurant/logic/commands/account/SelectAccountCommand.java new file mode 100644 index 000000000000..6d36a414e9ae --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/account/SelectAccountCommand.java @@ -0,0 +1,64 @@ +package seedu.restaurant.logic.commands.account; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.ui.accounts.DisplayAccountListRequestEvent; +import seedu.restaurant.commons.events.ui.accounts.JumpToAccountListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.account.Account; + +//@@author AZhiKai + +/** + * Selects an account identified using its displayed index from the account list. + */ +public class SelectAccountCommand extends Command { + + public static final String COMMAND_WORD = "select-account"; + + public static final String COMMAND_ALIAS = "sa"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Selects the account identified by the index number used in the displayed account list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SELECT_ACCOUNT_SUCCESS = "Selected account at index: %1$s"; + public static final String MESSAGE_INVALID_ACCOUNT_DISPLAYED_INDEX = "The account index provided is invalid"; + + private final Index targetIndex; + + public SelectAccountCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + List filteredAccountList = model.getFilteredAccountList(); + + if (targetIndex.getZeroBased() >= filteredAccountList.size()) { + throw new CommandException(MESSAGE_INVALID_ACCOUNT_DISPLAYED_INDEX); + } + EventsCenter.getInstance().post(new DisplayAccountListRequestEvent()); + EventsCenter.getInstance().post(new JumpToAccountListRequestEvent(targetIndex)); + return new CommandResult(String.format(MESSAGE_SELECT_ACCOUNT_SUCCESS, targetIndex.getOneBased())); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SelectAccountCommand // instanceof handles nulls + && targetIndex.equals(((SelectAccountCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/restaurant/logic/commands/exceptions/CommandException.java similarity index 81% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/seedu/restaurant/logic/commands/exceptions/CommandException.java index a16bd14f2cde..99b8412db6fd 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/restaurant/logic/commands/exceptions/CommandException.java @@ -1,9 +1,12 @@ -package seedu.address.logic.commands.exceptions; +package seedu.restaurant.logic.commands.exceptions; + +import seedu.restaurant.logic.commands.Command; /** * Represents an error which occurs during execution of a {@link Command}. */ public class CommandException extends Exception { + public CommandException(String message) { super(message); } diff --git a/src/main/java/seedu/restaurant/logic/commands/ingredient/AddIngredientCommand.java b/src/main/java/seedu/restaurant/logic/commands/ingredient/AddIngredientCommand.java new file mode 100644 index 000000000000..92a0d8a54a9c --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ingredient/AddIngredientCommand.java @@ -0,0 +1,73 @@ +package seedu.restaurant.logic.commands.ingredient; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_MINIMUM; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_NAME; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_PRICE; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_UNIT; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.ingredient.DisplayIngredientListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ingredient.Ingredient; + +//@@author rebstan97 +/** + * Adds an ingredient to the restaurant book. + */ +public class AddIngredientCommand extends Command { + + public static final String COMMAND_WORD = "add-ingredient"; + + public static final String COMMAND_ALIAS = "add-ing"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an ingredient to the restaurant book. " + + "Parameters: " + + PREFIX_INGREDIENT_NAME + "NAME " + + PREFIX_INGREDIENT_UNIT + "UNIT " + + PREFIX_INGREDIENT_PRICE + "PRICE " + + PREFIX_INGREDIENT_MINIMUM + "MINIMUM\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_INGREDIENT_NAME + "Cod Fish " + + PREFIX_INGREDIENT_UNIT + "kilogram " + + PREFIX_INGREDIENT_PRICE + "20.00 " + + PREFIX_INGREDIENT_MINIMUM + "5 "; + + public static final String MESSAGE_SUCCESS = "New ingredient added: %1$s"; + public static final String MESSAGE_DUPLICATE_INGREDIENT = "This ingredient already exists in the restaurant book"; + + private final Ingredient toAdd; + + /** + * Creates an AddIngredientCommand to add the specified {@code Ingredient} + */ + public AddIngredientCommand(Ingredient ingredient) { + requireNonNull(ingredient); + toAdd = ingredient; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.hasIngredient(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_INGREDIENT); + } + + model.addIngredient(toAdd); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayIngredientListRequestEvent()); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddIngredientCommand // instanceof handles nulls + && toAdd.equals(((AddIngredientCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/ingredient/DeleteIngredientByIndexCommand.java b/src/main/java/seedu/restaurant/logic/commands/ingredient/DeleteIngredientByIndexCommand.java new file mode 100644 index 000000000000..17396ee81e24 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ingredient/DeleteIngredientByIndexCommand.java @@ -0,0 +1,48 @@ +package seedu.restaurant.logic.commands.ingredient; + +import java.util.List; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.ui.ingredient.DisplayIngredientListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ingredient.Ingredient; + +//@@author rebstan97 +/** + * Deletes an ingredient identified using its displayed index from the ingredient book. + */ +public class DeleteIngredientByIndexCommand extends DeleteIngredientCommand { + + private final Index targetIndex; + + public DeleteIngredientByIndexCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + List lastShownList = model.getFilteredIngredientList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_INGREDIENT_DISPLAYED_INDEX); + } + + Ingredient ingredientToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteIngredient(ingredientToDelete); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayIngredientListRequestEvent()); + return new CommandResult(String.format(MESSAGE_DELETE_INGREDIENT_SUCCESS, ingredientToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteIngredientByIndexCommand // instanceof handles nulls + && (targetIndex.equals(((DeleteIngredientByIndexCommand) other).targetIndex))); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/ingredient/DeleteIngredientByNameCommand.java b/src/main/java/seedu/restaurant/logic/commands/ingredient/DeleteIngredientByNameCommand.java new file mode 100644 index 000000000000..d363acc3b1e1 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ingredient/DeleteIngredientByNameCommand.java @@ -0,0 +1,45 @@ +package seedu.restaurant.logic.commands.ingredient; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.events.ui.ingredient.DisplayIngredientListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ingredient.Ingredient; +import seedu.restaurant.model.ingredient.IngredientName; +import seedu.restaurant.model.ingredient.exceptions.IngredientNotFoundException; + +//@@author rebstan97 +/** + * Deletes an ingredient identified using its name from the restaurant book. + */ +public class DeleteIngredientByNameCommand extends DeleteIngredientCommand { + + private final IngredientName targetName; + + public DeleteIngredientByNameCommand(IngredientName targetName) { + this.targetName = targetName; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + try { + Ingredient ingredientToDelete = model.findIngredient(targetName); + model.deleteIngredient(ingredientToDelete); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayIngredientListRequestEvent()); + return new CommandResult(String.format(MESSAGE_DELETE_INGREDIENT_SUCCESS, ingredientToDelete)); + } catch (IngredientNotFoundException e) { + throw new CommandException(Messages.MESSAGE_INGREDIENT_NAME_NOT_FOUND); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteIngredientByNameCommand // instanceof handles nulls + && (targetName.equals(((DeleteIngredientByNameCommand) other).targetName))); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/ingredient/DeleteIngredientCommand.java b/src/main/java/seedu/restaurant/logic/commands/ingredient/DeleteIngredientCommand.java new file mode 100644 index 000000000000..2add98bab7de --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ingredient/DeleteIngredientCommand.java @@ -0,0 +1,35 @@ +package seedu.restaurant.logic.commands.ingredient; + +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; + +//@@author rebstan97 +/** + * Deletes an ingredient identified using its displayed index or name from the restaurant book. + */ +public abstract class DeleteIngredientCommand extends Command { + + public static final String COMMAND_WORD = "delete-ingredient"; + + public static final String COMMAND_ALIAS = "delete-ing"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the ingredient identified by the index number used in the displayed ingredient list or its " + + "name.\n" + + "Parameters: " + + "[INDEX (must be a positive integer)] " + + "[NAME] \n" + + "Examples: " + COMMAND_WORD + " 1, " + COMMAND_WORD + " Chicken Thigh"; + + public static final String MESSAGE_DELETE_INGREDIENT_SUCCESS = "Deleted Ingredient: %1$s"; + + @Override + public abstract CommandResult execute(Model model, CommandHistory history) throws CommandException; + + @Override + public abstract boolean equals(Object other); + +} diff --git a/src/main/java/seedu/restaurant/logic/commands/ingredient/EditIngredientByIndexCommand.java b/src/main/java/seedu/restaurant/logic/commands/ingredient/EditIngredientByIndexCommand.java new file mode 100644 index 000000000000..d52768de05cc --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ingredient/EditIngredientByIndexCommand.java @@ -0,0 +1,80 @@ +package seedu.restaurant.logic.commands.ingredient; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_INGREDIENTS; + +import java.util.List; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.ui.ingredient.DisplayIngredientListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ingredient.Ingredient; + +//@@author rebstan97 +/** + * Edits the details of an existing ingredient in the restaurant book. + */ +public class EditIngredientByIndexCommand extends EditIngredientCommand { + + private final Index targetIndex; + private final EditIngredientDescriptor editIngredientDescriptor; + + /** + * @param index of the ingredient in the filtered ingredient list to edit + * @param editIngredientDescriptor details to edit the ingredient with + */ + public EditIngredientByIndexCommand(Index index, EditIngredientDescriptor editIngredientDescriptor) { + requireNonNull(index); + requireNonNull(editIngredientDescriptor); + + this.targetIndex = index; + this.editIngredientDescriptor = new EditIngredientDescriptor(editIngredientDescriptor); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredIngredientList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_INGREDIENT_DISPLAYED_INDEX); + } + + Ingredient ingredientToEdit = lastShownList.get(targetIndex.getZeroBased()); + Ingredient editedIngredient = createEditedIngredient(ingredientToEdit, editIngredientDescriptor); + + if (!ingredientToEdit.isSameIngredient(editedIngredient) && model.hasIngredient(editedIngredient)) { + throw new CommandException(MESSAGE_DUPLICATE_INGREDIENT); + } + + model.updateIngredient(ingredientToEdit, editedIngredient); + model.updateFilteredIngredientList(PREDICATE_SHOW_ALL_INGREDIENTS); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayIngredientListRequestEvent()); + return new CommandResult(String.format(MESSAGE_EDIT_INGREDIENT_SUCCESS, editedIngredient)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditIngredientByIndexCommand)) { + return false; + } + + // state check + EditIngredientByIndexCommand e = (EditIngredientByIndexCommand) other; + return targetIndex.equals(e.targetIndex) + && editIngredientDescriptor.equals(e.editIngredientDescriptor); + } + +} diff --git a/src/main/java/seedu/restaurant/logic/commands/ingredient/EditIngredientByNameCommand.java b/src/main/java/seedu/restaurant/logic/commands/ingredient/EditIngredientByNameCommand.java new file mode 100644 index 000000000000..62bee3842195 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ingredient/EditIngredientByNameCommand.java @@ -0,0 +1,80 @@ +package seedu.restaurant.logic.commands.ingredient; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_INGREDIENTS; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.events.ui.ingredient.DisplayIngredientListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ingredient.Ingredient; +import seedu.restaurant.model.ingredient.IngredientName; +import seedu.restaurant.model.ingredient.exceptions.IngredientNotFoundException; + +//@@author rebstan97 +/** + * Edits the details of an existing ingredient in the restaurant book. + */ +public class EditIngredientByNameCommand extends EditIngredientCommand { + + private final IngredientName targetName; + private final EditIngredientDescriptor editIngredientDescriptor; + + /** + * @param name of the ingredient in the filtered ingredient list to edit + * @param editIngredientDescriptor details to edit the ingredient with + */ + public EditIngredientByNameCommand(IngredientName name, EditIngredientDescriptor editIngredientDescriptor) { + requireNonNull(name); + requireNonNull(editIngredientDescriptor); + + this.targetName = name; + this.editIngredientDescriptor = new EditIngredientDescriptor(editIngredientDescriptor); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + Ingredient ingredientToEdit; + Ingredient editedIngredient; + + try { + ingredientToEdit = model.findIngredient(targetName); + editedIngredient = createEditedIngredient(ingredientToEdit, editIngredientDescriptor); + } catch (IngredientNotFoundException e) { + throw new CommandException(Messages.MESSAGE_INGREDIENT_NAME_NOT_FOUND); + } + + if (!ingredientToEdit.isSameIngredient(editedIngredient) && model.hasIngredient(editedIngredient)) { + throw new CommandException(MESSAGE_DUPLICATE_INGREDIENT); + } + + model.updateIngredient(ingredientToEdit, editedIngredient); + model.updateFilteredIngredientList(PREDICATE_SHOW_ALL_INGREDIENTS); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayIngredientListRequestEvent()); + return new CommandResult(String.format(MESSAGE_EDIT_INGREDIENT_SUCCESS, editedIngredient)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditIngredientByNameCommand)) { + return false; + } + + // state check + EditIngredientByNameCommand e = (EditIngredientByNameCommand) other; + return targetName.equals(e.targetName) + && editIngredientDescriptor.equals(e.editIngredientDescriptor); + } + +} diff --git a/src/main/java/seedu/restaurant/logic/commands/ingredient/EditIngredientCommand.java b/src/main/java/seedu/restaurant/logic/commands/ingredient/EditIngredientCommand.java new file mode 100644 index 000000000000..093add79b53b --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ingredient/EditIngredientCommand.java @@ -0,0 +1,173 @@ +package seedu.restaurant.logic.commands.ingredient; + +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_MINIMUM; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_NAME; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_ORIGINAL_NAME; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_PRICE; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_UNIT; + +import java.util.Optional; + +import seedu.restaurant.commons.util.CollectionUtil; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ingredient.Ingredient; +import seedu.restaurant.model.ingredient.IngredientName; +import seedu.restaurant.model.ingredient.IngredientPrice; +import seedu.restaurant.model.ingredient.IngredientUnit; +import seedu.restaurant.model.ingredient.MinimumUnit; +import seedu.restaurant.model.ingredient.NumUnits; + +//@@author rebstan97 +/** + * Edits the details of an existing ingredient in the restaurant book. + */ +public abstract class EditIngredientCommand extends Command { + + public static final String COMMAND_WORD = "edit-ingredient"; + + public static final String COMMAND_ALIAS = "edit-ing"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the ingredient identified " + + "by the index number used in the displayed ingredient list or its name. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: [INDEX (must be a positive integer)] " + + "[" + PREFIX_INGREDIENT_ORIGINAL_NAME + "ORIGINAL_NAME] " + + "[" + PREFIX_INGREDIENT_NAME + "NAME] " + + "[" + PREFIX_INGREDIENT_UNIT + "UNIT] " + + "[" + PREFIX_INGREDIENT_PRICE + "PRICE] " + + "[" + PREFIX_INGREDIENT_MINIMUM + "MINIMUM] " + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_INGREDIENT_PRICE + "$5.60 " + + PREFIX_INGREDIENT_MINIMUM + "15 " + + "OR " + COMMAND_WORD + " " + + PREFIX_INGREDIENT_ORIGINAL_NAME + "chicken " + + PREFIX_INGREDIENT_PRICE + "$5.60 " + + PREFIX_INGREDIENT_MINIMUM + "15"; + + public static final String MESSAGE_EDIT_INGREDIENT_SUCCESS = "Edited Ingredient: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided"; + public static final String MESSAGE_DUPLICATE_INGREDIENT = "This ingredient already exists in the restaurant book"; + + @Override + public abstract CommandResult execute(Model model, CommandHistory history) throws CommandException; + + /** + * Creates and returns an {@code Ingredient} with the details of {@code ingredientToEdit} + * edited with {@code editIngredientDescriptor}. + */ + public static Ingredient createEditedIngredient(Ingredient ingredientToEdit, + EditIngredientDescriptor editIngredientDescriptor) { + assert ingredientToEdit != null; + + IngredientName updatedName = editIngredientDescriptor.getName().orElse(ingredientToEdit.getName()); + IngredientUnit updatedUnit = editIngredientDescriptor.getUnit().orElse(ingredientToEdit.getUnit()); + IngredientPrice updatedPrice = editIngredientDescriptor.getPrice().orElse(ingredientToEdit.getPrice()); + MinimumUnit updatedMinUnit = editIngredientDescriptor.getMinimum().orElse(ingredientToEdit.getMinimum()); + + NumUnits numToAdd = editIngredientDescriptor.getNumUnits().orElse(new NumUnits(0)); + NumUnits updatedNumUnits = ingredientToEdit.getNumUnits().increase(numToAdd.getNumberOfUnits()); + + return new Ingredient(updatedName, updatedUnit, updatedPrice, updatedMinUnit, updatedNumUnits); + } + + @Override + public abstract boolean equals(Object other); + + /** + * Stores the details to edit the ingredient with. Each non-empty field value will replace the + * corresponding field value of the ingredient. + */ + public static class EditIngredientDescriptor { + private IngredientName name; + private IngredientUnit unit; + private IngredientPrice price; + private MinimumUnit minimumUnit; + private NumUnits numUnits; + + public EditIngredientDescriptor() {} + + /** + * Copy constructor. + */ + public EditIngredientDescriptor(EditIngredientDescriptor toCopy) { + setName(toCopy.name); + setUnit(toCopy.unit); + setPrice(toCopy.price); + setMinimum(toCopy.minimumUnit); + setNumUnits(toCopy.numUnits); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, unit, price, minimumUnit, numUnits); + } + + public void setName(IngredientName name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setUnit(IngredientUnit unit) { + this.unit = unit; + } + + public Optional getUnit() { + return Optional.ofNullable(unit); + } + + public void setPrice(IngredientPrice price) { + this.price = price; + } + + public Optional getPrice() { + return Optional.ofNullable(price); + } + + public void setMinimum(MinimumUnit minimumUnit) { + this.minimumUnit = minimumUnit; + } + + public Optional getMinimum() { + return Optional.ofNullable(minimumUnit); + } + + public void setNumUnits(NumUnits numUnits) { + this.numUnits = numUnits; + } + + public Optional getNumUnits() { + return Optional.ofNullable(numUnits); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditIngredientDescriptor)) { + return false; + } + + // state check + EditIngredientDescriptor e = (EditIngredientDescriptor) other; + + return getName().equals(e.getName()) + && getUnit().equals(e.getUnit()) + && getPrice().equals(e.getPrice()) + && getMinimum().equals(e.getMinimum()) + && getNumUnits().equals(e.getNumUnits()); + } + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/ingredient/ListIngredientsCommand.java b/src/main/java/seedu/restaurant/logic/commands/ingredient/ListIngredientsCommand.java new file mode 100644 index 000000000000..4f61f6737a57 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ingredient/ListIngredientsCommand.java @@ -0,0 +1,33 @@ +package seedu.restaurant.logic.commands.ingredient; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_INGREDIENTS; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.ingredient.DisplayIngredientListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.model.Model; + +//@@author rebstan97 +/** + * Lists all ingredients in the restaurant book to the user. + */ +public class ListIngredientsCommand extends Command { + + public static final String COMMAND_WORD = "list-ingredients"; + + public static final String COMMAND_ALIAS = "list-ing"; + + public static final String MESSAGE_SUCCESS = "Listed all ingredients"; + + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.updateFilteredIngredientList(PREDICATE_SHOW_ALL_INGREDIENTS); + EventsCenter.getInstance().post(new DisplayIngredientListRequestEvent()); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/ingredient/LowStockCommand.java b/src/main/java/seedu/restaurant/logic/commands/ingredient/LowStockCommand.java new file mode 100644 index 000000000000..fffee3d7d873 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ingredient/LowStockCommand.java @@ -0,0 +1,37 @@ +package seedu.restaurant.logic.commands.ingredient; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.ingredient.DisplayIngredientListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ingredient.Ingredient; + +//@@author rebstan97 +/** + * Lists all ingredients in the restaurant book to the user. + */ +public class LowStockCommand extends Command { + + public static final String COMMAND_WORD = "low-stock"; + + public static final String COMMAND_ALIAS = "ls"; + + public static final String MESSAGE_SUCCESS = "Listed all ingredients that are low in stock count"; + + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + Predicate predicate = ingredient -> + ingredient.getNumUnits().getNumberOfUnits() < ingredient.getMinimum().getMinimumUnit(); + model.updateFilteredIngredientList(predicate); + EventsCenter.getInstance().post(new DisplayIngredientListRequestEvent()); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/ingredient/SelectIngredientCommand.java b/src/main/java/seedu/restaurant/logic/commands/ingredient/SelectIngredientCommand.java new file mode 100644 index 000000000000..4c358f95d18a --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ingredient/SelectIngredientCommand.java @@ -0,0 +1,63 @@ +package seedu.restaurant.logic.commands.ingredient; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.ui.ingredient.DisplayIngredientListRequestEvent; +import seedu.restaurant.commons.events.ui.ingredient.JumpToIngredientListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ingredient.Ingredient; + +//@@author rebstan97 +/** + * Selects an ingredient identified using its displayed index from the ingredient list. + */ +public class SelectIngredientCommand extends Command { + + public static final String COMMAND_WORD = "select-ingredient"; + + public static final String COMMAND_ALIAS = "select-ing"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Selects the ingredient identified by the index number used in the displayed ingredient list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SELECT_INGREDIENT_SUCCESS = "Selected Ingredient: %1$s"; + + private final Index targetIndex; + + public SelectIngredientCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + List filteredIngredientList = model.getFilteredIngredientList(); + + if (targetIndex.getZeroBased() >= filteredIngredientList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_INGREDIENT_DISPLAYED_INDEX); + } + EventsCenter.getInstance().post(new DisplayIngredientListRequestEvent()); + EventsCenter.getInstance().post(new JumpToIngredientListRequestEvent(targetIndex)); + return new CommandResult(String.format(MESSAGE_SELECT_INGREDIENT_SUCCESS, targetIndex.getOneBased())); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SelectIngredientCommand // instanceof handles nulls + && targetIndex.equals(((SelectIngredientCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/ingredient/StockUpCommand.java b/src/main/java/seedu/restaurant/logic/commands/ingredient/StockUpCommand.java new file mode 100644 index 000000000000..15b48a68697a --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/ingredient/StockUpCommand.java @@ -0,0 +1,86 @@ +package seedu.restaurant.logic.commands.ingredient; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_NAME; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_NUM; + +import java.util.HashMap; +import java.util.Map; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.events.ui.ingredient.DisplayIngredientListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ingredient.IngredientName; +import seedu.restaurant.model.ingredient.exceptions.IngredientNotFoundException; + +//@@author rebstan97 +/** + * Stocks up an ingredient in the restaurant book. + */ +public class StockUpCommand extends Command { + + public static final String COMMAND_WORD = "stockup"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Stocks up an ingredient in the restaurant book " + + "Parameters: " + + PREFIX_INGREDIENT_NAME + "NAME... " + + PREFIX_INGREDIENT_NUM + "NUM_OF_UNITS... \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_INGREDIENT_NAME + "Cod Fish " + + PREFIX_INGREDIENT_NUM + "20 "; + + public static final String MESSAGE_STOCKUP_INGREDIENT_SUCCESS = "Ingredient(s) stocked up: %1$s"; + + private final Map hashMap; + + /** + * Creates a StockUpCommand to stock up the specified {@code Ingredient} + */ + public StockUpCommand(Map ingredientHashMap) { + requireNonNull(ingredientHashMap); + this.hashMap = ingredientHashMap; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + try { + model.stockUpIngredients(hashMap); + } catch (IngredientNotFoundException e) { + throw new CommandException(Messages.MESSAGE_STOCKUP_INGREDIENT_NOT_FOUND); + } + + StringBuilder ingredientList = buildIngredientList(); + + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayIngredientListRequestEvent()); + return new CommandResult(String.format(MESSAGE_STOCKUP_INGREDIENT_SUCCESS, ingredientList)); + } + + /** + * Returns a string of ingredient names with their corresponding stocked up units. + */ + private StringBuilder buildIngredientList() { + StringBuilder stringBuilder = new StringBuilder(); + for (HashMap.Entry argPair : hashMap.entrySet()) { + IngredientName name = argPair.getKey(); + Integer numUnits = argPair.getValue(); + stringBuilder.append("\n" + numUnits.toString() + " units of " + name.toString()); + } + return stringBuilder; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof StockUpCommand // instanceof handles nulls + && hashMap.equals(((StockUpCommand) other).hashMap)); + } + +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/AddItemCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/AddItemCommand.java new file mode 100644 index 000000000000..59f0ca70be2d --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/AddItemCommand.java @@ -0,0 +1,70 @@ +package seedu.restaurant.logic.commands.menu; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_NAME; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_PRICE; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_TAG; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.menu.Item; + +//@@author yican95 +/** + * Adds an item to the menu. + */ +public class AddItemCommand extends Command { + + public static final String COMMAND_WORD = "add-item"; + + public static final String COMMAND_ALIAS = "ai"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an item to the menu.\n" + + "Parameters: " + + PREFIX_NAME + "ITEM_NAME " + + PREFIX_PRICE + "ITEM_PRICE " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "Apple Juice " + + PREFIX_PRICE + "2 " + + PREFIX_TAG + "drink"; + + public static final String MESSAGE_SUCCESS = "New item added: %1$s"; + public static final String MESSAGE_DUPLICATE_ITEM = "This item already exists in the menu"; + + private final Item toAdd; + + /** + * Creates an AddItemCommand to add the specified {@code Item} + */ + public AddItemCommand(Item item) { + requireNonNull(item); + toAdd = item; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.hasItem(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_ITEM); + } + + model.addItem(toAdd); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddItemCommand // instanceof handles nulls + && toAdd.equals(((AddItemCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/AddRequiredIngredientsCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/AddRequiredIngredientsCommand.java new file mode 100644 index 000000000000..a13bb35fcba3 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/AddRequiredIngredientsCommand.java @@ -0,0 +1,106 @@ +package seedu.restaurant.logic.commands.menu; + +import static seedu.restaurant.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_NAME; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_INGREDIENT_NUM; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ITEMS; + +import java.util.List; +import java.util.Map; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.commons.events.ui.menu.JumpToItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ingredient.IngredientName; +import seedu.restaurant.model.menu.Item; + +//@@author yican95 +/** + * Adds required ingredients to an item in the menu. + */ +public class AddRequiredIngredientsCommand extends Command { + public static final String COMMAND_WORD = "add-required-ingredients"; + public static final String COMMAND_ALIAS = "ari"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds the required ingredients for a item " + + "identified by the index number used in the displayed item list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_INGREDIENT_NAME + "INGREDIENT_NAME... " + + PREFIX_INGREDIENT_NUM + "NUMBER_OF_INGREDIENTS...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_INGREDIENT_NAME + "Apple " + + PREFIX_INGREDIENT_NUM + "3"; + + public static final String MESSAGE_ADD_REQUIRED_INGREDIENT_SUCCESS = "Added required ingredient(s) to Item: %1$s"; + public static final String MESSAGE_DELETE_REQUIRED_INGREDIENT_SUCCESS = "Removed required ingredient(s) from " + + "Item: %1$s"; + + private final Index index; + private final Map requiredIngredients; + + /** + * @param index of the item in the filtered item list to edit + * @param requiredIngredients the requiredIngredients of the item + */ + public AddRequiredIngredientsCommand(Index index, Map requiredIngredients) { + requireAllNonNull(index, requiredIngredients); + + this.index = index; + this.requiredIngredients = requiredIngredients; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + List lastShownList = model.getFilteredItemList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + + Item itemToEdit = lastShownList.get(index.getZeroBased()); + Item editedItem = new Item(itemToEdit.getName(), itemToEdit.getPrice(), itemToEdit.getRecipe(), + itemToEdit.getTags(), requiredIngredients); + + model.updateItem(itemToEdit, editedItem); + model.updateFilteredItemList(PREDICATE_SHOW_ALL_ITEMS); + model.commitRestaurantBook(); + + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + EventsCenter.getInstance().post(new JumpToItemListRequestEvent(index)); + return new CommandResult(generateSuccessMessage(editedItem)); + } + + /** + * Generates a command execution success message based on whether the recipe is added to or removed from + * {@code itemToEdit}. + */ + private String generateSuccessMessage(Item itemToEdit) { + String message = !requiredIngredients.isEmpty() ? MESSAGE_ADD_REQUIRED_INGREDIENT_SUCCESS + : MESSAGE_DELETE_REQUIRED_INGREDIENT_SUCCESS; + return String.format(message, itemToEdit); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof AddRequiredIngredientsCommand)) { + return false; + } + // state check + AddRequiredIngredientsCommand e = (AddRequiredIngredientsCommand) other; + return index.equals(e.index) + && requiredIngredients.equals(e.requiredIngredients); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/ClearMenuCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/ClearMenuCommand.java new file mode 100644 index 000000000000..a308b824e377 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/ClearMenuCommand.java @@ -0,0 +1,32 @@ +package seedu.restaurant.logic.commands.menu; + +import static java.util.Objects.requireNonNull; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.RestaurantBook; + +//@@author yican95 +/** + * Clears the menu. + */ +public class ClearMenuCommand extends Command { + + public static final String COMMAND_WORD = "clear-menu"; + public static final String COMMAND_ALIAS = "cm"; + public static final String MESSAGE_SUCCESS = "Menu has been cleared!"; + + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.resetMenuData(new RestaurantBook()); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/DeleteItemByIndexCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/DeleteItemByIndexCommand.java new file mode 100644 index 000000000000..1dff8e5d1d52 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/DeleteItemByIndexCommand.java @@ -0,0 +1,74 @@ +package seedu.restaurant.logic.commands.menu; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_ENDING_INDEX; + +import java.util.List; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.menu.Item; + +//@@author yican95 +/** + * Deletes an item identified using it's displayed index from the menu. + */ +public class DeleteItemByIndexCommand extends Command { + public static final String COMMAND_WORD = "delete-item-index"; + + public static final String COMMAND_ALIAS = "dii"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the item(s) identified by the index number used in the displayed item list.\n" + + "Parameters: INDEX (must be a positive integer, starting index) " + + "[" + PREFIX_ENDING_INDEX + "INDEX](cannot be smaller than the starting index)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_ITEM_SUCCESS = "Deleted %1$d items"; + + private final Index targetIndex; + private final Index endingIndex; + + public DeleteItemByIndexCommand(Index targetIndex, Index endingIndex) { + requireNonNull(targetIndex); + + this.targetIndex = targetIndex; + this.endingIndex = endingIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredItemList(); + + if (endingIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + + int numOfItems = endingIndex.getOneBased() - targetIndex.getZeroBased(); + + for (int i = endingIndex.getZeroBased(); i >= targetIndex.getZeroBased(); i--) { + Item itemToDelete = lastShownList.get(i); + model.deleteItem(itemToDelete); + } + + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + return new CommandResult(String.format(MESSAGE_DELETE_ITEM_SUCCESS, numOfItems)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteItemByIndexCommand // instanceof handles nulls + && targetIndex.equals(((DeleteItemByIndexCommand) other).targetIndex) + && endingIndex.equals(((DeleteItemByIndexCommand) other).endingIndex)); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/DeleteItemByNameCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/DeleteItemByNameCommand.java new file mode 100644 index 000000000000..b2e72dd55ed8 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/DeleteItemByNameCommand.java @@ -0,0 +1,59 @@ +package seedu.restaurant.logic.commands.menu; + +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ITEMS; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.menu.Item; +import seedu.restaurant.model.menu.Name; +import seedu.restaurant.model.menu.exceptions.ItemNotFoundException; + +//@@author yican95 +/** + * Deletes an item identified using its name from the menu. + */ +public class DeleteItemByNameCommand extends Command { + + public static final String COMMAND_WORD = "delete-item-name"; + + public static final String COMMAND_ALIAS = "din"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the item identified by name used in the displayed item list.\n" + + "Parameters: ITEM_NAME (must be in the displayed item list)\n" + + "Example: " + COMMAND_WORD + " Apple Juice"; + + public static final String MESSAGE_DELETE_ITEM_SUCCESS = "Deleted Item: %1$s"; + private final Name targetName; + + public DeleteItemByNameCommand(Name targetName) { + this.targetName = targetName; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + try { + Item itemToDelete = model.findItem(targetName); + model.deleteItem(itemToDelete); + model.updateFilteredItemList(PREDICATE_SHOW_ALL_ITEMS); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + return new CommandResult(String.format(MESSAGE_DELETE_ITEM_SUCCESS, itemToDelete)); + } catch (ItemNotFoundException e) { + throw new CommandException(Messages.MESSAGE_NAME_NOT_FOUND); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteItemByNameCommand // instanceof handles nulls + && (targetName.equals(((DeleteItemByNameCommand) other).targetName))); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/DiscountItemCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/DiscountItemCommand.java new file mode 100644 index 000000000000..47f7564f738d --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/DiscountItemCommand.java @@ -0,0 +1,136 @@ +package seedu.restaurant.logic.commands.menu; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_ENDING_INDEX; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_PERCENT; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ITEMS; + +import java.util.List; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.menu.Item; +import seedu.restaurant.model.menu.Price; + +//@@author yican95 +/** + * Gives an existing item in the menu a discount. + */ +public class DiscountItemCommand extends Command { + + public static final String COMMAND_WORD = "discount-item"; + + public static final String COMMAND_ALIAS = "dci"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Gives the item identified " + + "by the index number used in the displayed item list a discount based on the percentage. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX|ALL " + + "[" + PREFIX_ENDING_INDEX + "INDEX](cannot be smaller than the starting index) " + + PREFIX_PERCENT + "PERCENTAGE " + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PERCENT + "20"; + + public static final String MESSAGE_DISCOUNT_ITEM_SUCCESS = "Discounted %1$d items"; + public static final String MESSAGE_REVERT_ITEM_SUCCESS = "Reverted %1$d items"; + public static final String MESSAGE_NO_ITEM = "Menu is empty"; + + private Index index; + private Index endingIndex; + private final double percent; + private final boolean isAll; + + /** + * @param index of the item in the filtered item list to edit + * @param percent the percent of the discount + */ + public DiscountItemCommand(Index index, Index endingIndex, double percent, boolean isAll) { + requireNonNull(index); + requireNonNull(percent); + + this.index = index; + this.endingIndex = endingIndex; + this.percent = percent; + this.isAll = isAll; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredItemList(); + + if (isAll) { + try { + index = Index.fromZeroBased(0); + endingIndex = Index.fromOneBased(lastShownList.size()); + } catch (IndexOutOfBoundsException iooe) { + throw new CommandException(MESSAGE_NO_ITEM); + } + } + + if (endingIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + + int numOfItems = endingIndex.getOneBased() - index.getZeroBased(); + + for (int i = index.getZeroBased(); i < endingIndex.getOneBased(); i++) { + discountItem(model, Index.fromZeroBased(i), percent); + } + + model.updateFilteredItemList(PREDICATE_SHOW_ALL_ITEMS); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + String message = MESSAGE_DISCOUNT_ITEM_SUCCESS; + if (percent == 0) { + message = MESSAGE_REVERT_ITEM_SUCCESS; + } + return new CommandResult(String.format(message, numOfItems)); + } + + /** + * Update the itemToDiscount with the discountedItem in the model. + * @param model + * @param index + * @param percent + * @throws CommandException + */ + public static void discountItem(Model model, Index index, double percent) throws CommandException { + List lastShownList = model.getFilteredItemList(); + + Item itemToDiscount = lastShownList.get(index.getZeroBased()); + Item discountedItem = createDiscountedItem(itemToDiscount, percent); + + model.updateItem(itemToDiscount, discountedItem); + } + + /** + * Creates and returns a {@code Item} with the details of {@code itemToDiscount}. + */ + public static Item createDiscountedItem(Item itemToDiscount, double percent) { + assert itemToDiscount != null; + double originalValue = itemToDiscount.getPrice().getOriginalValue(); + Price updatedPrice = new Price(String.format("%.2f", originalValue)); + updatedPrice.setValue(percent); + + return new Item(itemToDiscount.getName(), updatedPrice, itemToDiscount.getRecipe(), itemToDiscount.getTags(), + itemToDiscount.getRequiredIngredients()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DiscountItemCommand // instanceof handles nulls + && index.equals(((DiscountItemCommand) other).index) // state check + && endingIndex.equals(((DiscountItemCommand) other).endingIndex) // state check + && isAll == ((DiscountItemCommand) other).isAll // state check + && percent == ((DiscountItemCommand) other).percent); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/EditItemCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/EditItemCommand.java new file mode 100644 index 000000000000..dc5540e01c71 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/EditItemCommand.java @@ -0,0 +1,210 @@ +package seedu.restaurant.logic.commands.menu; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_NAME; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_PRICE; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_TAG; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ITEMS; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.commons.util.CollectionUtil; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.ingredient.IngredientName; +import seedu.restaurant.model.menu.Item; +import seedu.restaurant.model.menu.Name; +import seedu.restaurant.model.menu.Price; +import seedu.restaurant.model.menu.Recipe; +import seedu.restaurant.model.tag.Tag; + +//@@author yican95 +/** + * Edits the details of an existing item in the menu. + */ +public class EditItemCommand extends Command { + + public static final String COMMAND_WORD = "edit-item"; + + public static final String COMMAND_ALIAS = "ei"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the item identified " + + "by the index number used in the displayed item list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "ITEM_NAME] " + + "[" + PREFIX_PRICE + "ITEM_PRICE] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PRICE + "2"; + + public static final String MESSAGE_EDIT_ITEM_SUCCESS = "Edited Item: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided"; + public static final String MESSAGE_DUPLICATE_ITEM = "This item already exists in the menu"; + + private final Index index; + private final EditItemDescriptor editItemDescriptor; + + /** + * @param index of the item in the filtered item list to edit + * @param editItemDescriptor details to edit the item with + */ + public EditItemCommand(Index index, EditItemDescriptor editItemDescriptor) { + requireNonNull(index); + requireNonNull(editItemDescriptor); + + this.index = index; + this.editItemDescriptor = new EditItemDescriptor(editItemDescriptor); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredItemList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + + Item itemToEdit = lastShownList.get(index.getZeroBased()); + Item editedItem = createEditedItem(itemToEdit, editItemDescriptor); + + if (!itemToEdit.isSameItem(editedItem) && model.hasItem(editedItem)) { + throw new CommandException(MESSAGE_DUPLICATE_ITEM); + } + + model.updateItem(itemToEdit, editedItem); + model.updateFilteredItemList(PREDICATE_SHOW_ALL_ITEMS); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + return new CommandResult(String.format(MESSAGE_EDIT_ITEM_SUCCESS, editedItem)); + } + + /** + * Creates and returns a {@code Item} with the details of {@code itemToEdit} + * edited with {@code editItemDescriptor}. + */ + private static Item createEditedItem(Item itemToEdit, EditItemDescriptor editItemDescriptor) { + assert itemToEdit != null; + + Name updatedName = editItemDescriptor.getName().orElse(itemToEdit.getName()); + Price updatedPrice = editItemDescriptor.getPrice().orElse(itemToEdit.getPrice()); + Set updatedTags = editItemDescriptor.getTags().orElse(itemToEdit.getTags()); + Recipe recipe = itemToEdit.getRecipe(); // edit command does not allow editing remarks and requiredIngredients + Map requiredIngredients = itemToEdit.getRequiredIngredients(); + + return new Item(updatedName, updatedPrice, recipe, updatedTags, requiredIngredients); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditItemCommand)) { + return false; + } + + // state check + EditItemCommand e = (EditItemCommand) other; + return index.equals(e.index) + && editItemDescriptor.equals(e.editItemDescriptor); + } + + /** + * Stores the details to edit the item with. Each non-empty field value will replace the + * corresponding field value of the item. + */ + public static class EditItemDescriptor { + private Name name; + private Price price; + private Set tags; + + public EditItemDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditItemDescriptor(EditItemDescriptor toCopy) { + setName(toCopy.name); + setPrice(toCopy.price); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, price, tags); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPrice(Price price) { + this.price = price; + } + + public Optional getPrice() { + return Optional.ofNullable(price); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditItemDescriptor)) { + return false; + } + + // state check + EditItemDescriptor e = (EditItemDescriptor) other; + + return getName().equals(e.getName()) + && getPrice().equals(e.getPrice()) + && getTags().equals(e.getTags()); + } + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/FilterMenuCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/FilterMenuCommand.java new file mode 100644 index 000000000000..360cf088bfa6 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/FilterMenuCommand.java @@ -0,0 +1,51 @@ +package seedu.restaurant.logic.commands.menu; + +import static java.util.Objects.requireNonNull; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.menu.TagContainsKeywordsPredicate; + +//@@author yican95 +/** + * Finds and lists all items in menu whose tags contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FilterMenuCommand extends Command { + + public static final String COMMAND_WORD = "filter-menu"; + + public static final String COMMAND_ALIAS = "fm"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all items whose tags contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD(must be alphanumeric) [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " monday tuesday wednesday"; + + private final TagContainsKeywordsPredicate predicate; + + public FilterMenuCommand(TagContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.updateFilteredItemList(predicate); + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + return new CommandResult( + String.format(Messages.MESSAGE_ITEMS_LISTED_OVERVIEW, model.getFilteredItemList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FilterMenuCommand // instanceof handles nulls + && predicate.equals(((FilterMenuCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/FindItemCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/FindItemCommand.java new file mode 100644 index 000000000000..b4854d97bd67 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/FindItemCommand.java @@ -0,0 +1,51 @@ +package seedu.restaurant.logic.commands.menu; + +import static java.util.Objects.requireNonNull; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.menu.NameContainsKeywordsPredicate; + +//@@author yican95 +/** + * Finds and lists all items in menu whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindItemCommand extends Command { + + public static final String COMMAND_WORD = "find-item"; + + public static final String COMMAND_ALIAS = "fi"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all items 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 + " apple burger cake"; + + private final NameContainsKeywordsPredicate predicate; + + public FindItemCommand(NameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.updateFilteredItemList(predicate); + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + return new CommandResult( + String.format(Messages.MESSAGE_ITEMS_LISTED_OVERVIEW, model.getFilteredItemList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindItemCommand // instanceof handles nulls + && predicate.equals(((FindItemCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/ListItemsCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/ListItemsCommand.java new file mode 100644 index 000000000000..5d5c1106f772 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/ListItemsCommand.java @@ -0,0 +1,33 @@ +package seedu.restaurant.logic.commands.menu; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ITEMS; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.model.Model; + +//@@author yican95 +/** + * Lists all items in the menu to the user. + */ +public class ListItemsCommand extends Command { + + public static final String COMMAND_WORD = "list-items"; + + public static final String COMMAND_ALIAS = "li"; + + public static final String MESSAGE_SUCCESS = "Listed all items"; + + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.updateFilteredItemList(PREDICATE_SHOW_ALL_ITEMS); + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/RecipeItemCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/RecipeItemCommand.java new file mode 100644 index 000000000000..6227e1ac16e0 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/RecipeItemCommand.java @@ -0,0 +1,100 @@ +package seedu.restaurant.logic.commands.menu; + +import static seedu.restaurant.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_RECIPE; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ITEMS; + +import java.util.List; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.commons.events.ui.menu.JumpToItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.menu.Item; +import seedu.restaurant.model.menu.Recipe; + +//@@author yican95 +/** + * Adds a recipe to a item in the menu. + */ +public class RecipeItemCommand extends Command { + public static final String COMMAND_WORD = "recipe-item"; + public static final String COMMAND_ALIAS = "ri"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds the recipe for a item identified " + + "by the index number used in the displayed item list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_RECIPE + "RECIPE\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_RECIPE + "Boil water over high heat and reduce the heat to medium-low, " + + "pour in the vinegar and 2 teaspoons of salt and egg."; + + public static final String MESSAGE_ADD_RECIPE_SUCCESS = "Added recipe to Item: %1$s"; + public static final String MESSAGE_DELETE_RECIPE_SUCCESS = "Removed recipe from Item: %1$s"; + + private final Index index; + private final Recipe recipe; + + /** + * @param index of the item in the filtered item list to edit + * @param recipe the recipe of the item + */ + public RecipeItemCommand(Index index, Recipe recipe) { + requireAllNonNull(index, recipe); + + this.index = index; + this.recipe = recipe; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + List lastShownList = model.getFilteredItemList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + + Item itemToEdit = lastShownList.get(index.getZeroBased()); + Item editedItem = new Item(itemToEdit.getName(), itemToEdit.getPrice(), recipe, itemToEdit.getTags(), + itemToEdit.getRequiredIngredients()); + + model.updateItem(itemToEdit, editedItem); + model.updateFilteredItemList(PREDICATE_SHOW_ALL_ITEMS); + model.commitRestaurantBook(); + + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + EventsCenter.getInstance().post(new JumpToItemListRequestEvent(index)); + return new CommandResult(generateSuccessMessage(editedItem)); + } + /** + * Generates a command execution success message based on whether the recipe is added to or removed from + * {@code itemToEdit}. + */ + private String generateSuccessMessage(Item editedItem) { + String message = !recipe.toString().isEmpty() ? MESSAGE_ADD_RECIPE_SUCCESS : MESSAGE_DELETE_RECIPE_SUCCESS; + return String.format(message, editedItem); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof RecipeItemCommand)) { + return false; + } + // state check + RecipeItemCommand e = (RecipeItemCommand) other; + return index.equals(e.index) + && recipe.equals(e.recipe); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/SelectItemCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/SelectItemCommand.java new file mode 100644 index 000000000000..0717209d91a2 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/SelectItemCommand.java @@ -0,0 +1,63 @@ +package seedu.restaurant.logic.commands.menu; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.commons.events.ui.menu.JumpToItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.menu.Item; + +//@@author yican95 +/** + * Selects an item identified using it's displayed index from the menu. + */ +public class SelectItemCommand extends Command { + + public static final String COMMAND_WORD = "select-item"; + + public static final String COMMAND_ALIAS = "si"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Selects the item identified by the index number used in the displayed item list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SELECT_ITEM_SUCCESS = "Selected Item: %1$s"; + + private final Index targetIndex; + + public SelectItemCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + List filteredItemList = model.getFilteredItemList(); + + if (targetIndex.getZeroBased() >= filteredItemList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ITEM_DISPLAYED_INDEX); + } + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + EventsCenter.getInstance().post(new JumpToItemListRequestEvent(targetIndex)); + return new CommandResult(String.format(MESSAGE_SELECT_ITEM_SUCCESS, targetIndex.getOneBased())); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SelectItemCommand // instanceof handles nulls + && targetIndex.equals(((SelectItemCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/SortMenuCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/SortMenuCommand.java new file mode 100644 index 000000000000..3a1ee6e6b164 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/SortMenuCommand.java @@ -0,0 +1,60 @@ +package seedu.restaurant.logic.commands.menu; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_ITEMS; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.model.Model; + +//@@author yican95 +/** + * Sorts and lists all items in menu either by name or price. + */ +public class SortMenuCommand extends Command { + + public static final String COMMAND_WORD = "sort-menu"; + + public static final String COMMAND_ALIAS = "sm"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sorts all items by name in lexicographical order or " + + "by price in ascending order " + + "and displays them as a list with index numbers.\n" + + "Parameters: SORTING METHOD(name or price)\n" + + "Example: " + COMMAND_WORD + " name"; + + public static final String MESSAGE_SORTED = "Menu has been sorted by %1$s"; + + /** + * Sorting Method that can be used + */ + public enum SortMethod { + NAME, PRICE + } + + private final SortMethod sortMethod; + + public SortMenuCommand(SortMethod sortMethod) { + this.sortMethod = sortMethod; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.sortMenu(sortMethod); + model.updateFilteredItemList(PREDICATE_SHOW_ALL_ITEMS); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + return new CommandResult(String.format(MESSAGE_SORTED, sortMethod.name())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortMenuCommand // instanceof handles nulls + && sortMethod.equals(((SortMenuCommand) other).sortMethod)); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/menu/TodaySpecialCommand.java b/src/main/java/seedu/restaurant/logic/commands/menu/TodaySpecialCommand.java new file mode 100644 index 000000000000..0c2bc1421072 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/menu/TodaySpecialCommand.java @@ -0,0 +1,75 @@ +package seedu.restaurant.logic.commands.menu; + +import static java.util.Objects.requireNonNull; + +import java.util.Calendar; +import java.util.Collections; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.events.ui.menu.DisplayItemListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.menu.TagContainsKeywordsPredicate; + +//@@author yican95 +/** + * Finds and lists all items in menu whose tags contains the day of the week. + */ +public class TodaySpecialCommand extends Command { + + public static final String COMMAND_WORD = "today-special"; + + public static final String COMMAND_ALIAS = "ts"; + + private final TagContainsKeywordsPredicate predicate; + + public TodaySpecialCommand(Calendar calendar) { + this.predicate = preparePredicate(calendar); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + model.updateFilteredItemList(predicate); + EventsCenter.getInstance().post(new DisplayItemListRequestEvent()); + return new CommandResult( + String.format(Messages.MESSAGE_ITEMS_LISTED_OVERVIEW, model.getFilteredItemList().size())); + } + + /** + * Parses DAY_OF_WEEK into a {@code TagContainsKeywordsPredicate}. + */ + public static TagContainsKeywordsPredicate preparePredicate(Calendar calendar) { + String str; + switch (calendar.get(Calendar.DAY_OF_WEEK)) { + case Calendar.SUNDAY: + str = "sunday"; + break; + case Calendar.MONDAY: + str = "monday"; + break; + case Calendar.TUESDAY: + str = "tuesday"; + break; + case Calendar.WEDNESDAY: + str = "wednesday"; + break; + case Calendar.THURSDAY: + str = "thursday"; + break; + case Calendar.FRIDAY: + str = "friday"; + break; + case Calendar.SATURDAY: + str = "saturday"; + break; + default: + str = ""; // Invalid Tag Name + break; + } + return new TagContainsKeywordsPredicate(Collections.singletonList(str)); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/reservation/AddReservationCommand.java b/src/main/java/seedu/restaurant/logic/commands/reservation/AddReservationCommand.java new file mode 100644 index 000000000000..35cba74357c2 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/reservation/AddReservationCommand.java @@ -0,0 +1,75 @@ +package seedu.restaurant.logic.commands.reservation; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_DATE; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_NAME; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_PAX; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_TAG; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_TIME; + +import seedu.restaurant.commons.core.EventsCenter; +import seedu.restaurant.commons.events.ui.reservation.DisplayReservationListRequestEvent; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.reservation.Reservation; + +/** + * Adds a reservation to the restaurant book. + */ +public class AddReservationCommand extends Command { + + public static final String COMMAND_WORD = "add-reservation"; + + public static final String COMMAND_ALIAS = "ar"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a reservation to the restaurant book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PAX + "PAX " + + PREFIX_DATE + "DATE " + + PREFIX_TIME + "TIME " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PAX + "4 " + + PREFIX_DATE + "05-12-2019 " + + PREFIX_TIME + "10:00 " + + PREFIX_TAG + "Driving"; + + public static final String MESSAGE_SUCCESS = "New reservation added: %1$s"; + public static final String MESSAGE_DUPLICATE_RESERVATION = "This reservation already exists in the restaurant book"; + + private final Reservation toAdd; + + /** + * Creates an AddReservationCommand to add the specified {@code Reservation} + */ + public AddReservationCommand(Reservation reservation) { + requireNonNull(reservation); + toAdd = reservation; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.hasReservation(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_RESERVATION); + } + + model.addReservation(toAdd); + model.commitRestaurantBook(); + EventsCenter.getInstance().post(new DisplayReservationListRequestEvent()); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddReservationCommand // instanceof handles nulls + && toAdd.equals(((AddReservationCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/reservation/DeleteReservationCommand.java b/src/main/java/seedu/restaurant/logic/commands/reservation/DeleteReservationCommand.java new file mode 100644 index 000000000000..43bc0bc2b126 --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/reservation/DeleteReservationCommand.java @@ -0,0 +1,59 @@ +package seedu.restaurant.logic.commands.reservation; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.reservation.Reservation; + +/** + * Deletes a reservation identified using it's displayed index from the restaurant book. + */ +public class DeleteReservationCommand extends Command { + + public static final String COMMAND_WORD = "delete-reservation"; + + public static final String COMMAND_ALIAS = "dr"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the reservation identified by the index number used in the displayed reservation list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_RESERVATION_SUCCESS = "Deleted Reservation: %1$s"; + + private final Index targetIndex; + + public DeleteReservationCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredReservationList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RESERVATION_DISPLAYED_INDEX); + } + + Reservation reservationToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteReservation(reservationToDelete); + model.commitRestaurantBook(); + return new CommandResult(String.format(MESSAGE_DELETE_RESERVATION_SUCCESS, reservationToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteReservationCommand // instanceof handles nulls + && targetIndex.equals(((DeleteReservationCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/restaurant/logic/commands/reservation/EditReservationCommand.java b/src/main/java/seedu/restaurant/logic/commands/reservation/EditReservationCommand.java new file mode 100644 index 000000000000..3eaf9afda1ea --- /dev/null +++ b/src/main/java/seedu/restaurant/logic/commands/reservation/EditReservationCommand.java @@ -0,0 +1,234 @@ +package seedu.restaurant.logic.commands.reservation; + +import static java.util.Objects.requireNonNull; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_DATE; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_NAME; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_PAX; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_TAG; +import static seedu.restaurant.logic.parser.util.CliSyntax.PREFIX_TIME; +import static seedu.restaurant.model.Model.PREDICATE_SHOW_ALL_RESERVATIONS; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.restaurant.commons.core.Messages; +import seedu.restaurant.commons.core.index.Index; +import seedu.restaurant.commons.util.CollectionUtil; +import seedu.restaurant.logic.CommandHistory; +import seedu.restaurant.logic.commands.Command; +import seedu.restaurant.logic.commands.CommandResult; +import seedu.restaurant.logic.commands.exceptions.CommandException; +import seedu.restaurant.model.Model; +import seedu.restaurant.model.reservation.Date; +import seedu.restaurant.model.reservation.Name; +import seedu.restaurant.model.reservation.Pax; +import seedu.restaurant.model.reservation.Remark; +import seedu.restaurant.model.reservation.Reservation; +import seedu.restaurant.model.reservation.Time; +import seedu.restaurant.model.tag.Tag; + +/** + * Edits the details of an existing reservation in the menu. + */ +public class EditReservationCommand extends Command { + + public static final String COMMAND_WORD = "edit-reservation"; + + public static final String COMMAND_ALIAS = "er"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the reservation identified " + + "by the index number used in the displayed reservation list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PAX + "PAX] " + + "[" + PREFIX_DATE + "DATE] " + + "[" + PREFIX_TIME + "TIME] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_PAX + "4"; + + public static final String MESSAGE_EDIT_RESERVATION_SUCCESS = "Edited Reservation: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_RESERVATION = "This reservation already exists."; + + private final Index index; + private final EditReservationDescriptor editReservationDescriptor; + + /** + * @param index of the reservation in the filtered reservation list to edit + * @param editReservationDescriptor details to edit the reservation with + */ + public EditReservationCommand(Index index, EditReservationDescriptor editReservationDescriptor) { + requireNonNull(index); + requireNonNull(editReservationDescriptor); + + this.index = index; + this.editReservationDescriptor = new EditReservationDescriptor(editReservationDescriptor); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredReservationList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_RESERVATION_DISPLAYED_INDEX); + } + + Reservation reservationToEdit = lastShownList.get(index.getZeroBased()); + Reservation editedReservation = createEditedReservation(reservationToEdit, editReservationDescriptor); + + if (!reservationToEdit.isSameReservation(editedReservation) && model.hasReservation(editedReservation)) { + throw new CommandException(MESSAGE_DUPLICATE_RESERVATION); + } + + model.updateReservation(reservationToEdit, editedReservation); + model.updateFilteredReservationList(PREDICATE_SHOW_ALL_RESERVATIONS); + model.commitRestaurantBook(); + return new CommandResult(String.format(MESSAGE_EDIT_RESERVATION_SUCCESS, editedReservation)); + } + + /** + * Creates and returns a {@code Reservation} with the details of {@code reservationToEdit} + * edited with {@code editReservationDescriptor}. + */ + private static Reservation createEditedReservation(Reservation reservationToEdit, + EditReservationDescriptor editReservationDescriptor) { + assert reservationToEdit != null; + + Name updatedName = editReservationDescriptor.getName().orElse(reservationToEdit.getName()); + Pax updatedPax = editReservationDescriptor.getPax().orElse(reservationToEdit.getPax()); + Date updatedDate = editReservationDescriptor.getDate().orElse(reservationToEdit.getDate()); + Time updatedTime = editReservationDescriptor.getTime().orElse(reservationToEdit.getTime()); + Remark updatedRemark = reservationToEdit.getRemark(); // edit command does not allow editing remarks + Set updatedTags = editReservationDescriptor.getTags().orElse(reservationToEdit.getTags()); + + return new Reservation(updatedName, updatedPax, updatedDate, updatedTime, updatedRemark, updatedTags); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditReservationCommand)) { + return false; + } + + // state check + EditReservationCommand e = (EditReservationCommand) other; + return index.equals(e.index) + && editReservationDescriptor.equals(e.editReservationDescriptor); + } + + /** + * Stores the details to edit the reservation with. Each non-empty field value will replace the + * corresponding field value of the reservation. + */ + public static class EditReservationDescriptor { + private Name name; + private Pax pax; + private Date date; + private Time time; + private Set tags; + + public EditReservationDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditReservationDescriptor(EditReservationDescriptor toCopy) { + setName(toCopy.name); + setPax(toCopy.pax); + setDate(toCopy.date); + setTime(toCopy.time); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(name, pax, date, time, tags); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPax(Pax pax) { + this.pax = pax; + } + + public Optional getPax() { + return Optional.ofNullable(pax); + } + + public void setDate(Date date) { + this.date = date; + } + + public Optional getDate() { + return Optional.ofNullable(date); + } + + public void setTime(Time time) { + this.time = time; + } + + public Optional