Developer Guide
- Acknowledgements
- Setting up, getting started
- Design
- Implementation
- Documentation, logging, testing, configuration, dev-ops
- Appendix: Requirements
- Appendix: Instructions for manual testing
- Appendix: Effort
Acknowledgements
- This project is based on the AddressBook-Level3 project created by the SE-EDU initiative.
Setting up, getting started
Refer to the guide Setting up and getting started.
Design
.puml
files used to create diagrams in this document docs/diagrams
folder. Refer to the
PlantUML Tutorial at se-edu/guides to learn how to create
and edit diagrams.
Architecture
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main
(consisting of classes
Main
and MainApp
)
is in charge of the app launch and shut down.
- At app launch, it initializes the other components in the correct sequence, and connects them up with each other.
- At shut down, it shuts down the other components and invokes cleanup methods where necessary.
The bulk of the app’s work is done by the following four components:
-
UI
: The UI of the App. -
Logic
: The command executor. -
Model
: Holds the data of the App in memory. -
Storage
: Reads data from, and writes data to, the hard disk.
Commons
represents a collection of classes used by multiple other components.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues
the command delete 1
.
Each of the four main components (also shown in the diagram above),
- defines its API in an
interface
with the same name as the Component. - implements its functionality using a concrete
{Component Name}Manager
class which follows the corresponding APIinterface
mentioned in the previous point.
For example, the Logic
component defines its API in the Logic.java
interface and implements its functionality
using the LogicManager.java
class which follows the Logic
interface. Other components interact with a given
component through its interface rather than the concrete class (reason: to prevent outside component’s being coupled
to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
UI component
The API of this component is specified in
Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PersonListPanel
,
StatusBarFooter
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class which captures
the commonalities between classes that represent parts of the visible GUI.
The UI
component uses the JavaFx UI framework. The layout of these UI parts are defined in matching .fxml
files
that are in the src/main/resources/view
folder. For example, the layout of the
MainWindow
is specified in
MainWindow.fxml
.
The UI
component,
- executes user commands using the
Logic
component. - listens for changes to
Model
data so that the UI can be updated with the modified data. - keeps a reference to the
Logic
component, because theUI
relies on theLogic
to execute commands. - depends on some classes in the
Model
component, as it displaysPerson
andAppointment
objects residing in theModel
.
Logic component
API :
Logic.java
Here’s a (partial) class diagram of the Logic
component:
The sequence diagram below illustrates the interactions within the Logic
component, taking execute("delete 1")
API
call as an example.
DeleteCommandParser
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
How the Logic
component works:
- When
Logic
is called upon to execute a command, it is passed to anAddressBookParser
object which in turn creates a parser that matches the command (e.g.,DeleteCommandParser
) and uses it to parse the command. - This results in a
Command
object (more precisely, an object of one of its subclasses e.g.,DeleteCommand
) which is executed by theLogicManager
. - The command can communicate with the
Model
when it is executed (e.g. to delete a person). - The result of the command execution is encapsulated as a
CommandResult
object which is returned back fromLogic
.
Here are the other classes in Logic
(omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
- When called upon to parse a user command, the
AddressBookParser
class creates anXYZCommandParser
(XYZ
is a placeholder for the specific command name e.g.,AddCommandParser
) which uses the other classes shown above to parse the user command and create aXYZCommand
object (e.g.,AddCommand
) which theAddressBookParser
returns back as aCommand
object. - All
XYZCommandParser
classes (e.g.,AddCommandParser
,DeleteCommandParser
, …) inherit from theParser
interface so that they can be treated similarly where possible e.g, during testing.
Model component
API :
Model.java
The Model
component,
- stores the address book data i.e., all
Person
objects (which are contained in aUniquePersonList
object). - stores the currently ‘selected’
Person
objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiableObservableList<Person>
that can be ‘observed’ e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. - stores a
UserPref
object that represents the user’s preferences. This is exposed to the outside as aReadOnlyUserPref
objects. - stores an
observableAppointments
object that represents existing appointments in the address book, sorted in a chronological order. - stores a
sortedAppointments
object that represents existing appointments in the address book. -
observableAppointments
andsortedAppointments
depend onfilteredPersons
. Hence, appointments listed are forPerson
objects infilteredPersons
. - does not depend on any of the other three components (as the
Model
represents data entities of the domain, they should make sense on their own without depending on other components).
Storage component
API :
Storage.java
The Storage
component,
- can save both address book data and user preference data in JSON format, and read them back into corresponding objects.
- inherits from both
AddressBookStorage
andUserPrefStorage
, which means it can be treated as either one (if only the functionality of only one is needed). - depends on some classes in the
Model
component (because theStorage
component’s job is to save/retrieve objects that belong to theModel
).
Common classes
Classes used by multiple components are in the seedu.addressbook.commons
package.
Implementation
This section describes some noteworthy details on how certain features are implemented.
Overview of How Commands Work
The basic idea of what happens when a user types a command:
- The LogicManager executes method is called and takes in the user’s input.
- The user’s input is then parsed by
AddressBookParser
, which then creates the respectiveXYZCommandParser
. -
XYZCommandParser
parses the additional arguments provided by the user and creates andXYZCommand
. -
XYZCommand
then communicates with ModelManager to execute and returns aCommandResult
which is displayed to the user.
The flow of how a Command
is executed is illustrated with the Schedule
Command below.
Schedule Command
Implementation Overview
After the AddressBookParser
identifies that the user’s input has a schedule command word, it creates a
ScheduleCommandParser
. The ScheduleCommandParser
then parses the users input and creates a new ScheduleCommand
containing an Appointment
and an Index
. The ScheduleCommand
is then executed by Logic Manager
, which updates
the Person
in Model
to have the created Appointment
. A CommandResult
which stores the message of the outcome
of schedule command is then returned. The partial class diagram is shown below.
In the event the Person
already has an existing appointment, a different instance of CommandResult
is instantiated. This
instance invokes the constructor that contains the Person
as well as the new proposed Appointment
. The returned
CommandResult
instead results in the UI being notified that while the ScheduleCommand
has been executed, the user
should be prompted to confirm this change on the OverrideWindow
of the UI. Only after confirmation of this is the
overriding of the appointment completed using the appointment and person stored in the CommandResult
. In the event of
cancelling the override, the program resumes its functionality, effectively discarding the execution of the rest of the
scheduleCommand
The following activity diagram summarises what happens the user executes a schedule command.
Design Considerations
Aspect: How to implement appointment for Person
Alternative 1 (Current Choice): Create an abstract class ScheduleItem and make it a compulsory field for Person.
The diagram below illustrates our current implementation. A Person
has is associated with 1 ScheduleItem
, which can be a NullAppointment
(empty appointment) or Appointment
.
- Pros:
- This ensures a 1-to-1 relationship between Person and Appointment, making implementation of other functions like sort easier. This also prevents clutter of appointments in the UI.
- This makes use of a facade design pattern, where
NullAppointment
andAppointment
will handle themselves without thePerson
knowing.
- Cons:
- This makes the scheduling of Appointments more inflexible, as the FA is unable to schedule multiple appointments with the same person.
- Other considerations:
-
NullAppointment
is a Singleton class to prevent multiple instances of it being created, making it more efficient for memory.
-
Alternative 2: Create a hashset of Appointments for each Person.
- Pros:
- More flexible, user can now schedule multiple appointment for a Person.
- Cons:
- Harder to implement operations such as editing of an appointment for a client. An additional step of finding the specified appointment within the hashset is required, which may potentially introduce more bugs.
- Harder to implement default behaviours for when person has no appointment.
Aspect: How to implement override prompt
Alternative 1(current solution): Create a separate constructor in CommandResult to handle overriding.
- Pros:
- Easy to implement.
- This “freezes” functionality of the program to force user to acknowledge or cancel the execution of the command.
- Cons:
- This creates multiple different constructors within the CommandResult.
Alternative 2: Abstract CommandResult to get a successfulExecutionResult and a PausedExecutionResult.
- Pros:
- Improves code readability and reduces coupling in code.
- Cons:
- Time-consuming to refactor code
- Improper implementation could result in breaking of coding principles.
- Note:
- With additional time, alternative 2 can be implemented by refactoring the code to create multiple subclasses. Be wary of the Liskov Substitution Principle (LSP) when doing so. The earlier alternative 2 is implemented, the better to reduce amount of code that needs to be refactored.
- The above implementation can be done in conjunction with the clear command prompt to reduce code coupling.
Complete Feature
Implementation Overview
The Complete feature is facilitated by the CompleteCommand
and CompleteCommandParser
. The
CompleteCommandParser
creates a CompleteByIndex
or CompleteByDate
object depending on the user’s input. Both CompleteByIndex
and CompleteByDate
extends CompleteCommand
as illustrated in the class diagram below.
The following sequence diagram illustrates how the complete operation is executed when date given.
The following activity diagram illustrates how the complete operation is executed.
Design Considerations
Alternative 1 (Previous Design): Use a CompleteCommandDescriptor
that has a Date
and Index
field wrapped by Java Optional
.
- Pros:
- Allows for clean, readable code without having to check for null values regardless of whether user inputs a date or index.
- Cons:
- Have to check for both fields for at every step of the command which is inefficient.
Alternative 2 (Current Choice): Make CompleteCommand
an abstract class with the subclass CompleteByIndex
and CompletebyDate
.
- Pros:
-
LogicManager
can just executeCompleteCommand
without needing to know if it isCompleteByIndex
orCompleteByDate
. - Also eliminates the need to check for null fields, since each
CompleteCommand
subclass only has their required fields. - This also increases the extensibility of the command, as a new subclass can just be added.
-
- Cons:
- Increases the amount of code written and testing required.
Gather Emails Feature
The Gather Emails feature in our software system is designed to efficiently collect email addresses. This feature is facilitated by the GatherCommand
and GatherCommandParser
. Below is the class diagram of the gather emails
feature.
Implementation Overview
The GatherCommand
is initiated by the GatherCommandParser
. The GatherCommandParser
parses the arguments and creates either a GatherEmailByFinancialPlan
or GatherEmailByTag
object respectively.
Both GatherEmailByFinancialPlan
or GatherEmailByTag
implements the GatherEmailPrompt
interface.
The GatherCommand
takes in the GatherEmailPrompt
object and passes it into the current Model
, subsequently interacting with the AddressBook
class.
The GatherCommand#execute()
executes the gather operation by calling Model#gatherEmails(GatherEmailPrompt prompt)
.
The following sequence diagram below shows how the gather operation works as described above:
The ModelManager#gatherEmails(GatherEmailPrompt prompt)
calls the AddressBook#gatherEmails(GatherEmailPrompt prompt)
method, which subsequently calls the UniquePersonsList#gatherEmails(GatherEmailsPrompt prompt)
method.
The UniquePersonsList
class maintains a list of unique persons. The UniquePersonsList
class implements the following operation:
-
UniquePersonsList#gatherEmails(GatherEmailPrompt prompt)
— Iterates through the persons list and callsGatherEmailPrompt#gatherEmails(Person person)
, passing in each person.
Depending on the type of GatherEmailPrompt
, the method above triggers either:
-
Person#gatherEmailsContainsTag(String prompt)
— Checks if the given prompt is a substring of anyTag
names in theSet<Tag>
of the current person. -
Person#gatherEmailsContainsFinancialPlan(String prompt)
— Checks if the given prompt is a substring of anyFinancialPlan
names in theSet<FinancialPlan>
of the current person.
These methods internally utilize Tag#containsSubstring(String substring)
and FinancialPlan#containsSubstring(String substring)
, respectively. These substring comparisons are performed in a case-insensitive manner by converting both the prompt and the FinancialPlan
or Tag
names to lowercase before the check.
By allowing partial input, users can efficiently find email addresses associated with lengthy Tag
or FinancialPlan
names. The case-insensitive approach enhances user-friendliness, ensuring consistent and reliable results, regardless of input case.
Currently, we only allow gathering emails by FinancialPlan
and Tag
fields as these are the more likely to be searched to gather emails by. However, additional classes implementing the GatherEmailPrompt
interface can be added to enable the gathering of emails based on a broader range of fields.
The following sequence diagram shows how the gather emails by FinancialPlan
field operation works:
The following activity diagram illustrates how the complete operation is executed:
Design Considerations
Aspect: How many inputs to accept
-
Alternative 1 (Current Choice): User can only search by one
FinancialPlan
orTag
.- Pros: Easy to implement. Limits the potential for bugs.
- Cons: Limited filtering options.
-
Alternative 2: User can search by multiple
FinancialPlan
andTag
fields.-
Pros: More flexible (e.g. gathering by a combination of
FinancialPlan
andTag
). - Cons: Introduces more complexity and requires additional error handling.
-
Pros: More flexible (e.g. gathering by a combination of
Expanded Find feature
The enhanced find mechanism is facilitated by the CombinedPredicate
and utilises the existing FindCommand
structure.
Implementation Overview
Here’s a sequence diagram that demonstrates how FindCommand
works:
The CombinedPredicate
class gives the find
command the ability to search for multiple terms at once, implemented using an array
of PersonContainsKeywordsPredicate
. Here’s a partial class diagram of the CombinedPredicate
.
All XYZContainsKeywordsPredicate
classes (e.g., NameContainsKeywordsPredicate
,
FinancialPlanContainsKeywordsPredicate
, …) inherit from the PersonContainsKeywordsPredicate
interface so that
they can be treated similarly in the CombinedPredicate
class.
Note that only NameContainsKeywordsPredicate
checks for whole words, because it is rare to search for people by
substrings e.g. Marc
and Marcus
should not show up in the same search. On the other hand,
FinancialPlanContainsKeywordsPredicate
and TagContainsKeywordsPredicate
allow matching for
substrings because there are certain cases where it is logical to search for substrings e.g. Plan A
and
Plan A Premium
are related, so they can show up in the same search.
The find
command format also changes to resemble a format more similar to the add
and edit
commands, to allow for
searching for keywords in multiple fields at the same time. We also allow the use of duplicate prefixes so that we
can search for multiple terms belonging to the same field.
For now, we only allow for searching for Name
, FinancialPlan
and Tag
fields because they are the most commonly
searched fields, but extending the feature to search in other fields is possible by creating the appropriate
Predicate
class and modifying the FindCommandParser
.
Design Considerations
Aspect: How to implement find for multiple fields
-
Alternative 1 (current choice): Use one unified command and format.
- Pros: Easy to implement (argument multimap is available), allows for more flexible usage.
- Cons: May get cluttered when there are many terms.
-
Alternative 2: Take an argument to decide which field to find by.
- Pros: More user-friendly and natural since there is no need to use prefixes.
- Cons: Less flexible, slightly more difficult to implement.
Aspect: How to implement CombinedPredicate
-
Alternative 1 (current choice): Use
varargs
and a common interface.- Pros: More flexible in usage while still testable.
- Cons: More difficult to modify and the check for equality can be defeated with enough effort.
-
Alternative 2: Compose it with the 3 component predicates.
- Pros: Easier to modify and test.
- Cons: Less flexible when trying to combine multiple predicates (that may be of the same type).
-
Alternative 3: Use a
Predicate<Person>
and use theor()
method to chain predicates.- Pros: More flexible in usage.
- Cons: More difficult to modify and test.
Sort Feature
The Sort feature in our software system is designed to sort the list of clients by name as well as appointment
time. This feature is facilitated through the SortCommand
class.
Implementation Overview
The following diagram summarises what happens when the user executes a sort command:
The SortCommand
class is instantiated by the SortCommandParser
, which parses user input commands. The
SortCommandParser
class implements the following operations:
-
SortCommandParser#parse(String args)
— Checks the sort command keyword passed in by the user.
The SortCommand
takes in a Comparator<Person>
object and passes it into the current Model
model. The
SortCommand
class implements the following operations:
-
SortCommand#execute()
— Executes the sort operation by callingmodel.sortFilteredPersonList(comparator)
.
The Model
interface is implemented by the ModelManager
, representing the in-memory model of the address book data.
It contains the following method:
- ModelManager#sortFilteredPersonList(Comparator
comparator)` — Carries out the sorting operation by setting the comparator on the list of clients wrapped in a SortedList wrapper.
A CommandResult
class is created and returned.
Design Considerations
Aspect: How Sort Executes
Alternative 1(current choice): User can sort by name and appointment at any time. As such, calling find on the sorted list will result in the ordering of find to also be sorted.
- Pros: Improved usability of maintaining order of list throughout without the list having to be reordered after each command.
- Cons: Limited sorting options as of now.
Appointment List Feature
Implementation Overview
The appointment list is facilitated by ModelManager
. It extends Model
and stores an additional SortedList<Appointment>
object that represents all existing appointments.
The setAppointmentList()
method checks against filteredPersons
to look for updates regarding existing Appointment
objects. The setAppointmentList()
method is called whenever there is a command that can potentially change the data stored, to ensure that the state of the appointment list is as updated as possible.
The getAppointmentList()
method is called once during the startup of the program by getAppointmentList()
in LogicManager
, which is in turn called by MainWindow
. It returns the sortedList<Appointment>
object within modelManager
.
Do note that appointments are inherently sorted by their date and time, with the earliest appointment showing up at the top.
The following sequence diagram shows how the appointment list is updated. The setPerson()
method is being called in the ScheduleCommand#execute()
method. The ModelManager#addToAppointmentIfPresent()
method adds an Appointment
object into the ObservableList<Appointment>
if the Person
object in filteredPersons
has a scheduleItem
object that is an instance of Appointment
.
Design Considerations
Aspect: Where to create SortedList<Appointment>
-
Alternative 1 (current choice): Implement it within
modelManager
- Pros:
SortedAppointments
object referencesfilteredPersons
which ensures that the appointment list corresponds withpersons
fromaddressBook
. In this implementation,persons
acts as the single source of truth which provides all information tomodelManager
. - Cons: Errors with respect to
addressBook
will affect the appointment list rendered.
- Pros:
-
Alternative 2: Implement it within
addressBook
- Pros:
persons
andappointmentList
are handled separately within.addressBook
and hence the appointment list is not dependent onpersons
inaddressBook
. - Cons:
filteredPersons
andsortedAppointments
might not correspond sincesortedAppointments
is no longer dependent onfilteredPersons
.
- Pros:
Aspect: How to update appointment list
-
Alternative 1 (current choice):
Empty
appointmentList
and populate the list when there is a command that can potentially update it.- Pros: Easy to implement.
- Cons: Time complexity might not be optimal if the application is dealing with large amounts of data.
-
Alternative 2:
Based on specific index that user inputs for commands, update
appointmentList
accordingly.- Pros: Time complexity will be optimal and will not deterioriate with increasing amounts of data.
- Cons: More difficult to test.
Documentation, logging, testing, configuration, dev-ops
Appendix: Requirements
Product scope
Target user profile: Financial Advisors
- has a need to manage a significant number of client contacts.
- prefer desktop apps over other types
- can type fast.
- prefers typing to mouse interactions.
- is reasonably comfortable using CLI apps.
Value proposition: This tool functions as a digital address book suited to the needs of financial advisors. It allows them to track, update, and manage their clients’ information efficiently. This is facilitated through the use of a command line interface for efficient and effective querying and modifying of clients’ data.
User stories
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * * |
financial advisor who often works with numerous clients | have a central repository for my clients’ contacts details | effectively manage the intricate details of each of my clients. |
* * * |
financial advisor | add clients’ contacts to the contact book | accumulate contacts for future purposes. |
* * * |
financial advisor | remove clients contacts from the contact book | keep my contact book compact and relevant. |
* * * |
financial advisor | edit clients’ contacts in the contact book | keep my information updated. |
* * |
financial advisor | record appointments with my clients | keep track of when my next meeting with the client is. |
* * |
financial advisor | tag my clients by the plans they purchase | gather groups of clients based on the financial plan(s) they purchased. |
* * |
financial advisor | search for clients with specific financial plans | update those people about their plans more efficiently. |
* * |
financial advisor | sort my clients in certain orders including alphabetical order or appointment time in both ascending and descending order | view my clients in a more systematic manner. |
* * |
financial advisor | view my upcoming appointments I have with clients in chronological order | better plan my time. |
* * |
financial advisor | complete appointments | clean up the address book of completed appointments. |
* * |
financial advisor | gather emails of clients by their tags such as age group | collate and notify people with the same tags on any updates. |
* * |
financial advisor | search for clients with the same financial plan | efficiently provide targeted updates to individuals with the same plan. |
* |
busy financial advisor | streamline administrative tasks like tracking my clients contacts | focus most of my time on giving personalised financial advice and services to my clients. |
* |
financial advisor managing a substantial client portfolio | follow a standardised format to collect my clients’ information | manage data consistency among my clients. |
* |
financial advisor | search for specific client details | quickly contact my clients. |
* |
busy financial advisor | have a warning prompt to confirm clearing of contact book | prevent accidental clearing of contact book |
* |
financial advisor with many appointments | have a warning when scheduling multiple appointments for the same person | receive reminders of appointments before making a new one |
Use cases
(For all use cases below, the System is UNOFAS
and the Actor is the financial advisor
, unless specified otherwise)
Use Case: UC01 - Show a list of all clients
Precondition: NIL
Guarantees: A list of all clients’ contact is shown.
MSS
- User requests to list all clients.
- UNOFAS shows a list of all clients.
Use case ends.
Extensions
- 2a. The list is empty.
Use case ends.
Use Case: UC02 - Add a client
Precondition: NIL
Guarantees: A client contact is added into UNOFAS only if the data entered is correct.
MSS
- User request to add a client to the list via the
add
command. - UNOFAS checks the correctness of the request.
-
UNOFAS adds the client and displays updated client list.
Use case ends.
Extensions
- 2a. User did not specify all required fields.
-
2a1. UNOFAS shows an error message.
Use case resumes at step 1.
-
Use Case: UC03 - Edit a client’s contacts
Precondition: NIL
Guarantees: A client contact is edited in UNOFAS only if the data entered is correct.
MSS
- User requests to list clients (UC01).
- User request to edit client’s contacts from the list via the
edit
command. - UNOFAS checks the correctness of the request.
-
UNOFAS changes the client’s contacts.
Use case ends.
Extensions
-
3a. User enters the wrong details.
-
3a1. UNOFAS shows an error message.
Use case resumes at step 2.
-
-
3b. There is a contact with the exact same name.
-
3b1. UNOFAS notifies user that contact exist.
Use case resumes at step 2.
-
Use Case: UC04 - Delete a client
Precondition: NIL
Guarantees: A client contact is deleted from UNOFAS only if the data entered is correct.
MSS
- User requests to list clients (UC01).
- User requests to delete a specific client in the list via the
delete
command. - UNOFAS checks the correctness of the request.
-
UNOFAS deletes the client.
Use case ends.
Extensions
-
3a. User inputs an invalid index.
-
3a1. UNOFAS shows an error message.
Use case resumes at step 2.
-
Use Case: UC05 - Find a client
Precondition: NIL
Guarantees: A list of clients that matches the query is displayed.
MSS
- User requests to find client.
- UNOFAS checks for the correctness of the request.
-
UNOFAS shows a list of clients which match search query
Use case ends.
Extensions
- 2a. User inputs invalid data for the command.
- 2a1. UNOFAS shows an error message. Use case resumes at step 1.
-
3a. The list is empty.
Use case ends.
Use Case: UC06 - Assign financial plan to a client
Precondition: NIL
Guarantees: A financial plan is assigned to a client in UNOFAS only if the data entered is correct.
MSS
- User requests to list clients (UC01).
- UNOFAS shows a list of clients.
- User request to add financial plan to client’s contacts (UC03).
- UNOFAS checks for the correctness of the request.
-
UNOFAS changes the client’s contacts.
Use case ends.
Extensions
-
4a. User enters the wrong details.
-
4a1. UNOFAS shows an error message.
Use case resumes at step 1.
-
Use Case: UC07 - Sort client’s contacts
Precondition: NIL
Guarantees: The contact list will be sorted in ascending order according to the sort function specified.
MSS
- User requests to list clients (UC01)
- UNOFAS shows a list of clients.
- User requests to sort list of clients (by appointment time or name) via
sort
command. - UNOFAS checks for the correctness of the request.
-
UNOFAS updates ordering of clients’ contacts.
Use case ends.
Extensions
- 4a. User enters the wrong details.
- 4a1. UNOFAS shows an error message. Use case resumes at step 1.
Use Case: UC08 - Schedule appointment for a client
Precondition: Client must exist before scheduling appointment.
Guarantees: An appointment is scheduled for a client in UNOFAS only if the data entered is correct.
MSS
- User requests to list clients (UC01)
- UNOFAS shows a list of clients.
- User request to schedule appointment for client via the
schedule
command. - UNOFAS checks the correctness of the request.
-
UNOFAS changes the client’s contacts.
Use case ends.
Extensions
- 4a. User enters the wrong details.
- 4a1. System shows an error message. Use case resumes at step 1.
- 4b. Client’s contact has an existing appointment scheduled.
- 4b1. UNOFAS shows a warning message.
- 4b2. User confirms to replace the existing appointment. Use case resumes at step 5.
Use Case: UC09 - Complete appointment for a client
Precondition: Appointment and client must exist before completing appointment.
Guarantees: An appointment is completed for a client in UNOFAS only if the data entered is correct.
MSS
- User requests to list clients (UC01)
- UNOFAS shows a list of clients.
- User requests to complete appointment for client via the
complete
command. - UNOFAS checks for the correctness of the command.
-
UNOFAS removes appointment from appointment list and client’s contact card
Use case ends.
Extensions
- 4a. User enters the wrong details.
- 4a1. UNOFAS shows an error message. Use case resumes at step 3.
- 4b. Client’s contact chosen does not have an existing appointment scheduled.
- 4b1. UNOFAS shows a warning message. Use case ends.
- 4c. No clients’ appointments in contacts matches date input by user.
- 4c1. UNOFAS shows a warning message. Use case ends.
Non-Functional Requirements
- Should work on any mainstream OS as long as it has Java
11
or above installed. - Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
- A user with typing speed of above 80WPM 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.
- A user should be able to have up to 2000 clients.
- The product is offered as a free offline service.
- The codebase should be well-documented to aid in future maintenance and updates.
- Should continue working despite invalid commands and error messages should be shown to the user.
- All features added to the code should be tested.
- All commands should be able to be executed by a financial advisor with little technical knowledge.
Planned Enhancements
- The current
schedule
command does not check if the given date is before the current date, so it is vulnerable to user error. We plan to make the command check for the date and fail if the date is before the current date:Date given cannot be before the current date
. - The current
phone
andnext-of-kin phone
fields currently only accepts numbers. It cannot accept international number formats. We plan to make the fields accept symbols so numbers such as+6598765432
and001-234-1-4610818
will be accepted. This will involve changing the validity checker for both fields. - The current contact book does not check for duplicates beyond the exact matching of the person’s
name
. We plan to refuse adding/editing of a person’s details if it results in two people sharing aname
(case-insensitive) orphone
since two people are very unlikely to share those details. - The current
name
andnext-of-kin name
fields currently do not accept symbols. We plan to make the fields accept symbols so that names likeThaarshen s/o Thaarshen
andO'Brien
are accepted. This will involve changing the validity checker for both fields. - The current
gather
command does not allow the gathering of all emails in the contact book or by multiple fields at once. To allow the gathering of all the persons emails usinggather all
command, we plan create anotherGatherEmailPrompt
class, with a method that will call the PersongetEmail()
method. To allow gathering emails by multiple fields, for example using thefp/
andt/
prefixes at once, we plan to use a similar approach tofind
but return the person’s email instead. - The
clear
command confirmation window can be manipulated using the arrow and ‘Enter’ keys. The window is initialised with the focus on theconfirm
button. This makes it possible for a user to accidentally press ‘Enter’ twice and wipe the contact book anyway, bypassing the defence mechanism entirely. We plan to make the command more resistant to mistakes by having the user key in a specific phrase, or to initialise the window with the focus on thecancel
button instead. - The
schedule
andcomplete
command current uses the samed/
prefix but the format for the arguments is different for both. We plan to update add a newdt/
prefix to represent date-time arguments forschedule
command to prevent confusion for the users.
Glossary
- Mainstream OS: Windows, Linux, UNIX, OS-X.
- Private contact detail: A contact detail that is not meant to be shared with others.
- API: Application Programming Interface that enables application to use capabilities or data from another application.
- Appointment : An arrangement to meet someone at a particular time, in this case, a client.
- Financial Advisor: A person who provides financial advice and sells financial plans to prospective clients.
- Financial Products: A product connected with the way a person manages or uses money (e.g. Insurance).
- Client: A person whose financial products are being managed by a financial advisor.
- Portfolio value: The intrinsic value of all financial products being held under a clients name.
- Central Repository: A centralised storage location for all user data.
- Contact details: Name, email, phone number, next-of-kin name, next-of-kin phone number, home address, financial plan(s), tag(s) and appointment(if any) of a client.
- Lexicographical: Generalisation of alphabetical order to include symbols or elements of a totally ordered set.
Appendix: Instructions for manual testing
Given below are instructions to test the app manually.
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.
-
-
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.
-
Adding a person
-
Adding a person.
-
Prerequisites: List all persons using the
list
command. -
Test case:
add n/name p/987 a/address e/email@email.com nk/nokname nkp/654
Expected: New contact with the above details is added to the bottom of the list. -
Test case:
add n/invalidName! p/987 a/address e/email@email.com nk/nokname nkp/654
Expected: No person is added. Error details shown in the status message. Status bar remains the same.
-
Editing a person
-
Editing an existing person.
-
Prerequisites: List all persons using the
list
command. At least 1 person in the list. -
Test case:
edit 1 n/New Name
Expected: The first contact in the list has their name changed toNew Name
. -
Test case:
edit 1 n/Invalid Name!
Expected: The name of the first contact is unchanged. Error details shown in the status message. Status bar remains the same.
-
Deleting a person
-
Deleting a person while all persons are being shown.
-
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)
Expected: Similar to previous.
-
-
Deleting a person while not all persons are being shown
- Prerequisites: List all persons using the
list
command, then filter the list using thefind
command. At least 1 person in the remaining list and at least 1 person filtered out. - Test case:
delete 1
Expected: Same as with the full list. - Test case: Delete a number that is equal to the number of people in the full contact book.
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
- Prerequisites: List all persons using the
Finding a person
-
Finding a person by name.
-
Prerequisites: List all persons using the
list
command. At least 1 person in the contact book with the nameTest Name
. -
Test case:
find n/Name
Expected: The person with the nameTest Name
appears in the filtered contact book. -
Test case:
find n/Invalid Name!
Expected: List remains unchanged. Error details shown in the status message. Status bar remains the same.
-
-
Finding a person by tag or financial plan can be tested in a similar manner as above.
Gathering emails
-
Gathering emails by tag.
-
Prerequisites: At least 1 person in the contact book with the tag
TestTag
. -
Test case:
gather t/Tag
Expected: The email of the person with the tagTestTag
appears in the status message. -
Test case:
gather t/WrongTag!
Expected: Error details shown in the status message. Status bar remains the same.
-
-
Gathering emails by financial plan can be tested in a similar manner as above.
Sorting the contact book
-
Sorting by names.
-
Prerequisites: List all persons using the
list
command. At least 2 people in the contact book. -
Test case:
sort name
Expected: The contact book is sorted by names in alphabetical order. -
Test case:
sort names
Expected: List remains unchanged. Error details shown in the status message. Status bar remains the same.
-
-
Sorting by appointments can be tested in a similar manner as above.
Scheduling an appointment
-
Scheduling an appointment.
-
Prerequisites: List all persons using the
list
command. At least 1 person in the contact book. -
Test case:
schedule 1 ap/Appointment Name d/11-11-2025 09:00
Expected: The first person in the list is updated to contain the appointment details. The appointment list is updated as well. -
Test case:
schedule 1 ap/Appointment Name d/11-30-2025 09:00
Expected: Error details shown in the status message. List, status bar and appointment list remains the same. -
Test case:
schedule 1 ap/Appointment Name d/12-11-2025 09:00
on a person who already has an appointment
Expected: A prompt will appear that causes program functionality to temporarily stop. The prompt alerts the user that the client already has an appointment arranged and the appointment will be overriden if the user wishes to proceed. After proceeding, the old appointment is overridden and the app continues its notmal functionality.
-
Completing an appointment
-
Completing by index.
-
Prerequisites: List all persons using the
list
command. At least 1 person in the contact book with a scheduled appointment. -
Test case:
complete 1
Expected: Appointment details removed from the first person in the list. The appointment list is updated as well. -
Test case:
complete 0
Expected: Error details shown in the status message. List, status bar and appointment list remains the same.
-
-
Completing by appointment date.
-
Prerequisites: List all persons using the
list
command. Exactly 2 people in the contact book with a scheduled appointment on11-11-2025
. -
Test case:
complete d/11-11-2025
Expected: Appointment details removed from the 2 people in the list. The appointment list is updated as well.
-
Clearing data
- User wishes to clear the addressbook.
- Test case:
clear
Expected: A prompt will appear that causes program functionality to temporarily stop. The prompt alerts the user that he intends to clear the address book and asks for confirmation of the clear. After proceeding, the entire address book will be cleared.
- Test case:
Saving data
- Dealing with missing data files
- If there is no saved data, the application will open with a new data file loaded with sample data
- To do this:
- Go to the location of the saved data. The location of the saved data can be found at the bottom left of the UNOFAS app.
- Delete the file
addressbook.json
. - Restart the application.
- A new file with sample contacts and appointments will be created.
- Dealing with corrupted data files
- If saved data is corrupted, the application will wipe the corrupted data and restart with no contacts and appointments.
- To simulate a corrupted file:
- Go to the location of the saved data.
- Open
addressbook.json
and corrupt the file in a way that makes it an invalid file to read (e.g. adding alphabets into a contact’s phone number field) - Restart the application.
- A new file will be created with no contacts and appointments.
Appendix: Effort
This project required a substantial effort to design and implement various features aimed at enhancing the functionality of the software system. It was quite hard at the beginning because we were not well-versed with the codebase. After understanding some pertinent classes to implement our enhancements, we also had to refactor and add test cases to ensure the functionalities of our enhancements.
In v1.2, we implemented the Sort command to allow users to sort his clients’ contacts in alphabetical order with
respect to their names, or with respect to their appointment time in chronological order. This posed a significant
challenge as there was no clear documentation that provided aid as to how we should implement both the filtering function (via
the find command) and the sort command at the same time. We had to spend time understanding the inner workings of
JavaFX’s filteredList
and sortedList
classes to finally come up with a solution to return a sorted and filtered
list of clients.
In v1.3, we updated the GUI to include an appointment list to show upcoming appointments that clients have with the financial advisor. We found out that the appointment list is not properly updated when there are changes made to the data, and they are only updated upon restarting the application. We had to spend time understanding how the Observer Pattern works so that changes to the appointment list are being reflected instantaneously.
Moreover, we decided to implement safety features like the clear and override prompts that prevent accidental command executions by the user. This required having to trace the entire code logic to understand how commands were executed and also how to pause and split execution of commands to make sure that no bugs were introduced into the code.
The project’s difficulty level was notably high due to the complexity of implementing features such as scheduling appointments, gathering emails, expanded find functionality, sorting, and the introduction of an appointment list.