By: Team AY1920S1-CS2103-F10-1      Since: Jun 2019      Licence: MIT

1. Setting up

Refer to the guide here.

2. Design

2.1. Architecture

ArchitectureDiagram
Figure 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.

Main has two classes called Main and 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.

Commons represents a collection of classes used by multiple other components. The following class plays an important role at the architecture level:

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of 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.

Each of the four components

  • Defines its API in an interface with the same name as the Component.

  • Exposes its functionality using a {Component Name}Manager class.

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.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

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 pat-delete 1.

ArchitectureSequenceDiagram
Figure 3. Component interactions for pat-delete 1 command

The sections below give more details of each component.

2.2. UI component

UiClassDiagram
Figure 4. Structure of the UI Component

API : 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.

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 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.

Manager classes like AutoCompletePanelManager and DataPanelsTabPaneManager handles changes of its respective components.

UiPartObserver interface prevents direct communication between 2 UiPart. It is implemented by the result display and autocomplete panel to listen for userinput from the command box and make changes acccordingly.

2.3. Logic component

LogicClassDiagram
Figure 5. Structure of the Logic Component

API : Logic.java

  1. Logic uses the PatientBookParser class to parse the user command.

  2. This results in a Command object which is executed by the LogicManager.

  3. The command execution can affect the Model (e.g. adding a person).

  4. The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui.

  5. In addition, the CommandResult object can also instruct the Ui to perform certain actions, such as displaying help to the user.

Given below is the Sequence Diagram for interactions within the Logic component for the execute("delete 1") API call.

DeleteSequenceDiagram
Figure 6. Interactions Inside the Logic Component for the delete 1 Command
The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

2.4. Model component

ModelClassDiagram
Figure 7. Structure of the Model Component

API : Model.java

The Model,

  • stores a UserPref object that represents the user’s preferences.

  • stores the PatientBook, AppointmentBook, and HistoryManager data.

  • exposes unmodifiable ObservableList<Patient>, ObservableList<Appointment>, and ObservableList<HistoryRecord> that can be 'observed' i.e. the UI can be bound to these lists so that the UI automatically updates when the data in the lists change.

  • does not depend on any of the other three components.

2.5. Storage component

StorageClassDiagram
Figure 8. Structure of the Storage Component

API : Storage.java

The Storage component,

  • can save UserPref objects in json format and read it back.

  • can save the Patient Book data in json format and read it back.

  • can save the Appointment Book data in json format and read it back.

2.6. Common classes

Classes used by multiple components are in the cs.f10.t1.nursetraverse.commons package.

3. Implementation

This section describes some noteworthy details on how certain features are implemented.

3.1. Undo/Redo/History

This set of functionality consists of one GUI component and two commands, each of which rely on the same core components:

  1. HistoryPane - GUI component that displays a numbered list of previous data-modifying commands that can be reverted by app-undo.

  2. app-undo - Command that reverts until a specified data-modifying command in the history, or the previous data-modifying command if no argument is specified.

  3. app-redo - Command that reverts the previous command if and only if it was an app-undo.

3.1.1. Overview of Changes

For undo/redo/history to function, there must be the following:

  • A way to mark which Command s are data-modifying commands so that non-data-modifying commands can be ignored.

  • A mechanism to clearly demarcate the model state before and after the execution of each data-modifying command.

  • A way to record the state of the model before (or after) each command, together with the Command object responsible.

  • Methods to switch the state of the model back and forth from the states stored in the history.

To achieve this, the following classes are introduced:

  • MutatorCommand - An empty subclass of Command to indicate that a Command is a data-modifying command and should be considered for undo/redo/history.

  • HistoryRecord - Record of the command and data of one atomic commit in the command history. It consists of the MutatorCommand responsible for the commit and the state of the model either before or after the commit. All recorded states are deep copies decoupled from their original references.

  • HistoryManager - Represents the history of the application. Maintains data structures containing HistoryRecord s of each data state change.

Additionally, LogicManager and Model are modified to accommodate this functionality.

Shallow Copy vs Deep copy
  • Performing a shallow copy of an object simply copies the values of the references of each object the original object is pointing to. This results in the copied object pointing to the same objects as the original. In the figure below, patientBook and copiedPatientBook are separate PatientBook objects but actually share the same patient object. Changes to patient through PatientBook would thus affect copiedPatientBook as well.

shallow copy
Figure 9. Shallow copy
  • Performing a deep copy creates duplicates of each object referenced by the original, the objects referenced by those duplicates, and so on. These duplicates are completely decoupled from their originals. In the figure below, patient is a different object from copiedPatient, thus changes to patient would not affect copiedPatient.

deep copy
Figure 10. Deep copy

The following methods are added to the ModelManager class:

  • Model#commit(MutatorCommand) - Commits the changes made to the address book since the last call to this method, making them permanent and updating the UI data. Creates a new HistoryRecord containing the committing MutatorCommand and the PatientBook and AppointmentBook states before the execution of the command and pushes it to the HistoryManager for storage.

  • Model#getHistory() - Returns an unmodifiable view of the history.

  • Model#undoTo(HistoryRecord) - Reverts current model state to the that contained in the specified HistoryRecord (i.e. the state before the command was executed).

  • Model#redo() - Redoes the previous MutatorCommand if it was an undo by popping the latest HistoryRecord from the HistoryManager 's redo stack.

Furthermore, in addition to MutatorCommand which was described earlier, the following logical classes are added:

  1. UndoCommand - Undoes a designated command in the history, or the previous one if no argument is specified. The COMMAND_WORD for this command is app-undo.

  2. UndoCommandParser - Parses input arguments and creates a new UndoCommand object.

  3. RedoCommand - Redoes the previous command if it was an undo. The COMMAND_WORD for this command is app-redo.

HistoryManager checks the classes of commands pushed to the history and does not record them if they are instances of UndoCommand or RedoCommand. This ensures that successive UndoCommand s do not undo themselves instead of the desired data-modifying commands, requiring RedoCommand for the special case of undo reversion.

3.1.2. Example Usage Scenario

The following example usage scenario and the state of historyManager at each step.

Step 1: The user launches the application.

HistoryManager is initialized with empty history and redoStack objects.

UndoRedoState0
Figure 11. Initial state of historyManager

Step 2: The user executes the command pat-delete 3.

After the DeleteCommand makes its changes on the model, logicManager calls Model#commit(MutatorCommand), passing the command object into modelManager. In turn, modelManager passes the command object, stagedPatientBook, and stagedAppointmentBook into historyManager through HistoryManager#pushRecord(MutatorCommand, PatientBook, AppointmentBook).

historyManager uses those objects to create a new HistoryRecord object which contains the model state before the command was executed (here labelled hr0) and pushes it into the history.

UndoRedoState1
Figure 12. historyManager after pat-delete 3 is executed

Step 3: The user executes the command visit-start.

The interaction between logicManager, modelManager and historyManager is the same as before.

historyManager creates a new HistoryRecord object (here labelled hr1) and pushes it into the history.

UndoRedoState2
Figure 13. historyManager after visit-start is executed

Step 4: The user wants to revert to the first item in the history, so he executes the command app-undo 1.

First, the UndoCommand retrieves the HistoryRecord corresponding to the first item in the history by searching the list returned by Model#getHistory(). Then, UndoCommand calls Model#undoTo(HistoryRecord) passing in the target record. When this happens, modelManager calls historyManager#popRecordsTo(HistoryRecord) to pop all records after and including the target record from the history (hr1 and hr0 in the previous step). The historyManager uses these popped records to create new records of the model state after the commands were executed, and places these new records (hr2 and hr3) into the redoStack.

UndoRedoState3
Figure 14. historyManager after app-undo 1 is executed

Finally, modelManager calls ModelManager#changeBaseTo(PatientBook, AppointmentBook) using the state objects in the target record. This performs the actual reversion of the state.

The action of the UndoCommand is summarised in the sequence diagram below:

UndoSequenceDiagram
Figure 15. Sequence diagram for app-undo

Step 5: The user wants to redo pat-delete 3, so he executes the command app-redo.

The RedoCommand calls Model#redo(). The modelManager calls HistoryManager#popRedo(PatientBook, AppointmentBook) passing it the current stagedPatientBook and stagedAppointmentBook.

historyManager pops the record at the top of the redoStack (hr3) and uses its command, together with the PatientBook and AppointmentBook just passed in by the modelManager, to create a new HistoryRecord (hr4) describing the model state before that command was executed. It then pushes hr4 into the history.

UndoRedoState4
Figure 16. historyManager after app-redo is executed

historyManager#popRedo() returns the HistoryRecord (hr3) containing the state after the redo. modelManager can now call ModelManager#changeBaseTo() to change the state to it.

Step 6: The user executes the command pat-clear.

The action of the logicManager and modelManager is similar what is described in Step 2; only this time, when modelManager calls HistoryManager#pushRecord(MutatorCommand, PatientBook, AppointmentBook), the historyManager sees that the committing command is not an UndoCommand or a RedoCommand and clears the redoStack to avoid branching.

UndoRedoState5
Figure 17. historyManager after pat-clear is executed

3.1.3. Model and Logic Design Considerations

Aspect: How to undo and redo between states
  • Alternative 1 (current choice): Save the entire PatientBook and AppointmentBook objects to record each model state.

    • Pros: Easier to implement.

    • Cons: Consumes more memory.

  • Alternative 2: Only save the MutatorCommand objects but implement an undo() method for each MutatorCommand which does exactly the reverse of its execute() method.

    • Pros: Consumes much less memory.

    • Cons: Difficult to implement - doubles the amount of work needed for each command.

Aspect: How to record the PatientBook and AppointmentBook states in the history
  • Alternative 1: Simply store references to PatientBook and AppointmentBook.

    • Pros: Easier to implement.

    • Cons: Relies on the assumption that the objects in PatientBook and AppointmentBook are immutable; if they are not truly immutable, changes to the current model’s PatientBook and AppointmentBook state may leak and affect the states stored in the history.

  • Alternative 2 (current choice): Defensively store deep copies of the PatientBook and AppointmentBook.

    • Pros: Prevents improperly coded Patient or Appointment (or their associated classes) from breaking undo/redo/history functionality. Can reuse JSON serialization code for persistent storage of PatientBook and AppointmentBook to create deep copies by serializing then immediately deserializing them.

    • Cons: Consumes more memory and CPU time. More difficult to implement - MVC pattern between UI view and models is broken in two. This is because each time the current state is swapped with a state in the history by ModelManager, the ObservableList viewed by the UI must also be updated by the ModelManager instead of the PatientBook as the current PatientBook is completely decoupled and placed into the history.

Aspect: Which class to place the HistoryManager in
  • Alternative 1 (current choice): Make HistoryManager a field of ModelManager.

    • Pros: Ensures atomicity of the records in the history as pushing a transaction to the HistoryManager can only be (and is always) done by Model#commit() itself - records in the history are guaranteed to be products of complete command execution rather than intermediate states.

    • Cons: More difficult to test ModelManager as two ModelManager objects may have the same current state but differing HistoryManager objects. May violate Single Responsibility Principle as ModelManager now has to manage both its current state and its previous states.

  • Alternative 2: Make HistoryManager a field of LogicManager.

    • Pros: Higher cohesion as ModelManager only represents the model’s current state. Easier to test ModelManager as only its current state matters.

    • Cons: It is possible for intermediate model states to be pushed to the HistoryManager - trusts LogicManager to push the transaction to history after (and only after) calling Model#commit(). Requires Command#execute() to accept HistoryManager as a parameter just so UndoCommand and RedoCommand can work even though the vast majority of commands do not require it.

3.1.4. UI

The command history is constantly displayed in a panel on the right side of the app. This HistoryPanel uses HistoryRecordCard s to display the user-input text that invoked each command. It is a view of the ObservableList<HistoryRecord> returned by HistoryManager#asUnmodifiableObservableList().

3.1.5. UI Design Considerations

Aspect: Where to display the history
  • Alternative 1 (current choice): Permanently display it in a dedicated panel.

    • Pros: User does not have to execute a 'history' command to view the history, making it much easier to use the multiple undo function.

    • Cons: Takes up more space in the UI.

  • Alternative 2: Display it as a tab in the TabPane.

    • Pros: Saves space in the UI.

    • Cons: User has to switch to the history tab to view it. Less intuitive UX as the other tabs in the TabPane all display actual data such as Patient, Visit, and Appointment info, whereas history is app metadata.

3.2. Visits Management

Visit Management has 3 sections to it: the model that models the visits, the logic that allows users to manipulate these models and the UI.

3.2.1. Model

How Visits, VisitTodos and VisitTasks are stored can be seen in Section 2.4, “Model component” above. To support the feature of ongoing visits and the constraint that there can only be 1 ongoing visit at a time, a Pair<Integer, Integer> named pairOfOngoingPatAndVisitIndexes is stored in PatientBook. This serves multiple purposes:

  • To identify the Patient that is currently being visited and the Visit that is ongoing

  • To ensure that the state is saved even if the application shuts down during an ongoing visit

  • To ensure there is no duplication of data by storing only the indexes for reference

  • Although there is a reference to the Patient in the Visit class, this reference is only implied in the JSON file. To maximize efficiency such that the application does not have to search every patient to find an ongoing visit every time the application is opened, the Patient needs to be stored together with the Visit as a pair in PatientBook.

Model implements the following operations:

  • Model#setNewOngoingVisit(Visit visit) — Record a new ongoing Visit of person in the model. This Visit must be retrieved unmodified from a Patient, or an IllegalArgumentException will be thrown (so only use this to begin visits).

  • Model#updateOngoingVisit(Visit updatedVisit) — Update an ongoing Visit in the model. This will update the ongoing Visit and update the Visit in the Patient. Use this to update an ongoing Visit when there is already a Visit.

  • Model#unsetOngoingVisit() — Set the ongoing visit of person in the model and patient book to null (for cancelling or deleting visits).

  • Model#patientHasOngoingVisit(Person person) — Returns true if the person has an ongoing visit.

  • Model#getObservableOngoingVisitList() — Returns a ObservableList<Visit> containing the ongoing Visit if there is an ongoing Visit.

3.2.2. Model Design Considerations

Aspect: How to reflect changes in ongoing visit on UI
  • Alternative 1 (current choice): Use an ObservableList<Visit> returned from FXCollections.observableArrayList().

    • Pros: Easier to implement.

    • Cons: May not make logical sense as there will only be 0..1 ongoing Visit s at any point in time.

  • Alternative 2: Manually trigger an update every time the ongoing Visit is updated.

    • Pros: Makes logical sense as there will only be 0..1 ongoing Visit s at any point in time.

    • Cons: Complex, increased coupling between UI and Model. These lead to an increase of work (and potentially errors) for the developer.

3.2.5. UI

A Patient 's Visit s, VisitTodo are displayed together with his details on pat-list and pat-find in a TitledPane.

An ongoing Visit is displayed in another tab named "Current Visit" using OngoingVisitListPanel containing OngoingVisitCard which contains VisitTaskTableView. Likewise, pat-list and pat-find use FinishedVisitCard s each containing a VisitTaskTableView to display the Visit s and VisitTask s.

Changing of tabs on running the appropriate command

The application currently does not change tabs when an appropriate command is run (e.g. if you’re on the Patient tab and you run visit-now-update, it will stay on the Patient tab). This is actually by design as it allows users to multitask across tabs.

If in future iterations there is a need to change the tab on running an appropriate command, there are some considerations on how to implement the changing of tabs programmatically (e.g. when someone executes the command to begin a visit), which necessitates some additional coupling between Model and Ui. The proposed implementation may be to use the Observer pattern: Model would contain a subject that would notify its observers whenever a component (e.g. Patient or Visit) is listed/added/updated/deleted/etc, and Ui could observer this subject and update itself accordingly.

3.2.6. UI Design Considerations

Aspect: How to display the Patient 's Visit records (and his VisitTodo s)

  • Alternative 1 (current choice): Embed it with pat-list and pat-find.

    • Pros: Easier to implement, convenient for user (don’t have to call methods such as visit-view which will only show the visit)

    • Cons: May clutter screen.

  • Alternative 2: Implement functions such as visit-view etc.

    • Pros: A cleaner result page from pat-list and pat-find.

    • Cons: Increased developer work and user will have to use multiple commands to view the Patient 's Visit data.

Aspect: How to display the ongoing Visit

  • Alternative 1 (current choice): Put the PersonListPanel into a TabPane and put the OngoingVisitListPanel in the same TabPane

    • Pros: Neater.

    • Cons: No real cons apart from the fact that the user will have to click to change tabs.

  • Alternative 2: Open a new window to show the ongoing Visit.

    • Pros: Can be viewed side by side with the main application.

    • Cons: Increases clutter on user screen, increases complexity in handling scenario where user closes the window.

3.2.7. Use case

Visit Management is used when the user wants to visit a patient and record their visit. There are 4 actions that a user can perform:

VisitManagementUseCase

The possible workflow a user can take when these set of actions is depicted in the following diagram. Take note that the application will always start without an ongoing visit and this assumption is implied in the activity diagram. Also note that the terms "visit" and "ongoing visit" are interchangeable, as a user can only cancel, update or finish an ongoing visit.

VisitActivityDiagram

The most complex part of this use case is in when the user decides to update the ongoing visit. The sequence of events when updating an ongoing visit is detailed in this sequence diagram. Note that this sequence diagram is not exhaustive to help you understand without going into too much detail. (e.g. Exception handling is omitted and in-depth code is placed into ref fragments which are not included in this developer guide).

UpdateOngoingVisitSequenceDiagram

3.3. Appointment Management

3.3.1. Implementation Overview

The appointments management feature is facilitated primarily by the AppointmentBook, a class that implements the ReadOnlyAppointmentBook interface and wraps around all appointments data of the app. This is a new book (different from the PatientBook) that is now required by the ModelManager class for instantiation along with UserPrefs and PatientBook. The AppointmentBook is, therefore, a field in the ModelManager class. The AppointmentBook has appointments, which is a UniqueAppointmentList, as a private field that holds all appointments currently in the AppointmentBook as a unique list. This list is modified from certain method calls in the ModelManager class.

3.3.2. Model

The main operations in the AppointmentBook are exposed to in the Model interface. Model implements the following operations to accommodate for the inclusion of the AppointmentBook and its related methods:

  • Model#setStagedAppointmentBook(ReadOnlyAppointmentBook appointmentBook) — Replaces appointment book in the Model with data from the ReadOnlyAppointmentBook appointmentBook passed in as argument.

  • Model#replaceStagedAppointmentBook(List<Appointment> appointments) — Replaces all appointments in appointment book with new appointments from the list.

  • Model#getStagedAppointmentBook() — Returns the current AppointmentBook.

  • Model#hasAppointment(Appointment appointment) — Returns true if an appointment with the same identity as the appointment passed in as argument exists in the appointment list.

  • Model#hasClashingAppointment(Appointment appointment) — Returns true if any appointment in the appointment list has clashing time with the appointment passed in as argument.

  • Model#deleteAppointment(Appointment target) — Deletes the given appointment. If the the target appointment is recurring, then the appointment is recurred and the next one is added to the appointment list.

  • Model#deleteRecurringAppointment(Appointment target) — Deletes the given recurring or non-recurring appointment permanently.

  • Model#deleteAppointments(Patient target, Index targetIndex) — Deletes all appointments associated with the target patient.

  • Model#addAppointment(Appointment appointment) — Adds the given appointment. The appointment passed in as argument must not already exist in the appointment list.

  • Model#setAppointment(Appointment target, Appointment editedAppointment) — Replaces the given appointment target with editedAppointment. target must exist in the appointment list. The appointment identity of editedAppointment must not be the same as another existing appointment in the appointment list.

  • Model#setAppointments(Patient patientToEdit, Patient editedPatient) — Replaces all appointments' patients that equal patientToEdit with editedPatient.

  • Model#ObservableList<Appointment> getStagedAppointmentList() — Returns an unmodifiable view of the entire appointment list.

  • Model#FilteredList<Appointment> getFilteredAppointmentList() — Returns an unmodifiable view of the filtered appointment list.

  • Model#void updateFilteredAppointmentList(Predicate<Appointment> predicate) — Updates the filter of the filtered appointment list to filter by the given predicate.

3.3.3. Model Design Considerations

Aspect: Where to wrap all appointments data in
  • Alternative 1 (current choice): Create AppointmentBook to wrap around all appointments and add it as a field of ModelManager.

    • Pros:

      • Splitting the AddressBook into PatientBook and AppointmentBook clearly demarcates the handling of patients and appointments separately.

      • When there are changes made to the appointments, only the AppointmentBook is modified, i.e. there is no need to, for example, refresh the data for patients.

    • Cons:

      • Overhead of more methods in the Model needed separately for the changes made in the AppointmentBook.

      • Major refactoring was needed in many classes and tests since there were changes to the constructor and fields of the ModelManager.

  • Alternative 2: Modify the original AddressBook to have two unique lists as fields within it, one for patients and one for appointments.

    • Pros:

      • The number of extra methods added to the Model could have been reduced since some methods may do the same operation on both appointment and patient lists.

    • Cons:

      • Principle of separation of concerns may be violated.

      • Optimization might be compromised, since methods intended to operate only on the specific patient or appointment lists might operate on the whole AddressBook unnecessarily.

3.3.4. Logic

The following logical Parser classes to parse user commands' arguments, and the corresponding Command classes they returned were implemented:

  1. FindAppointmentParser returns FindAppointmentCommand - Finds and lists all appointments in appointment book whose name contains any of the argument keywords.
    The COMMAND_WORD for this command is appt-find.

  2. AddAppointmentParser returns AddAppointmentCommand - Adds an appointment to the appointment list.
    The COMMAND_WORD is appt-add.

  3. EditAppointmentParser returns EditAppointmentCommand - Edits the details of an existing appointment in the appointment book.
    The COMMAND_WORD is appt-edit.

  4. DeleteAppointmentParser returns DeleteAppointmentCommand - Deletes an appointment identified using it’s displayed index from the appointment book. For recurring appointments, the recurring appointment is deleted and the next recurred one is added to the appointment list.
    The COMMAND_WORD is appt-delete.

  5. DeleteAppointmentPermanentlyParser returns DeleteAppointmentPermanentlyCommand - Deletes permanently a recurring appointment identified using it’s displayed index from the appointment book.
    The COMMAND_WORD is appt-delete-permanent.

  6. ListAppointmentCommand - Lists all appointments in the appointment book. Note: The ListAppointmentCommand does not have a parser as it does not have any arguments to parse.
    The COMMAND_WORD is appt-list.

3.3.5. Logic Design Considerations

Aspect: Deleting recurring appointments permanently
  • Alternative 1 (current choice): Create DeleteAppointmentPermanentlyParser and DeleteAppointmentPermanentlyCommand as new classes independent of the DeleteAppointmentParser and DeleteAppointmentCommand classes.

    • Pros:

      • A simple solution that clearly separates cases of when an appointment is deleted permanently from the list versus when the appointment is deleted and the recurred next appointment is added to the list.

    • Cons:

      • Does not really separate the cases of recurring appointments from non-recurring ones since appt-delete-permanent can be applied on both recurring and non-recurring appointments.

      • There might be fragments of code in both classes that are common and could be extracted out and reused instead.

  • Alternative 2: Create DeleteAppointmentPermanentlyParser and DeleteAppointmentPermanentlyCommand as new classes that now extend from the DeleteAppointmentParser and DeleteAppointmentCommand classes.

    • Pros:

      • By extending, code reusability is increased.

      • It also makes sense in terms of class structure for such an extension, since deleting an appointment permanently is a more specific case of deleting an appointment and also allowing to recur it and then add it back (if it was a recurring appointment).

    • Cons:

      • This still does not resolve the issue of separation deletion of recurring appointments from non-recurring ones.

      • Despite extending, there may not be too much code overlap. Also, considering that new methods will have to be added to the Model for each case of permanent and non-permanent deletion, the extension might well prove to be unecessary.

  • Alternative 3: Create DeleteRecurringAppointmentParser and DeleteRecurringAppointmentCommand as new classes independent from the DeleteAppointmentParser and DeleteAppointmentCommand classes.
    But DeleteRecurringAppointmentCommand can only be applied to appointments that are recurring.

    • Pros:

      • Demarcates recurring and non-recurring appointments deletion better since the user will now have only one way to delete non-recurring appointments, i.e. through appt-delete.

    • Cons:

      • Can be a bit restrictive since one command is reserved for only recurring appointments.

      • The names of these classes can be a bit misleading since appt-delete can still be applied on both recurring and non-recurring appointments.

3.3.6. Storage

The AppointmentBook and Appointment class have the JsonSerializableAppointmentBook and JsonAdaptedAppointment classes respectively to allow saving of appointments data to appointmentBook.json and reading data from it.

The reading of appointments data from storage currently does not check if there are clashing appointments. Hence, any modifications to the appointmentBook.json directly (i.e. not through tha app) that result in clashing appointments will be just loaded as normal onto the app, but lead to undesirable results.

3.3.7. Storage Design Considerations

Aspect: How to handle appointments that have finished (i.e. are before the system date and time)
  • Alternative 1 (current choice): AppointmentBook has two fields of UniqueAppointmentList: appointments and finishedAppointments.
    When data is read from the storage, all appointments that are past the system date and time are stored in the appointments list, while those that are finished are stored in the finishedAppointments list.
    After the app is closed, all appointments in the finishedAppointments list are not saved.

    • Pros:

      • Ensures a clear distinction between appointments that have finished and those that are still only scheduled and yet to happen. *

    • Cons:

      • Throws away the finished appointments every time app is closed.

      • No indication to the user that certain appointments finished and so are no longer displayed in the list of appointments.

  • Alternative 2: As before, but once the app is closed, appointments both in appointments and finishedAppointments list are saved.

    • Pros:

      • Still ensures a clear distinction between appointments that have finished and those that are still only scheduled and yet to happen.

      • Keeps the finished appointments.

    • Cons:

      • Might need to handle the reading and saving of the finished appointments differently from the main appointment list.

      • Need to consider whether user deleting an appointment implies the user wants it marked as finished or wants to remove it completely. If the former is the case, the delete appointment command would need to modify the finishedAppointments list, and that is an overhead.

3.3.8. UI

The list of Appointment s are displayed on a separate tab in the app.

3.4. Use Case

Appointment Management is used when the user wants to schedule an appointment with the patient. There are 6 actions that a user can perform:

AppointmentManagementUseCase

Perhaps the most complex workflow is when a user deletes a recurring appointment. In doing so, as indicated in the Use Case diagram above, the appointment is first deleted from the list of appointments. The appointment is then recurred to get the next appointment (i.e. with the next start and end date time), and added to the list of appointments by directly calling the AppointmentBook#addRecurringAppointment method. Assuming the first appointment in the appointment list is recurring. The following sequence diagrams show the object interactions for user command appt-delete 1:

AppointmentManagementSequenceDiagram

Figure: Delete Appointment Sequence Diagram

AppointmentManagementSequenceDiagramRef

Figure: Reference - delete appointment, recur, and add the recurred appointment

3.5. Import and Export

Import and export functionality are split into three commands:

  1. app-export

  2. app-import-replace

  3. app-import-merge

3.5.1. Jackson Library

The current import and export features are built upon the the existing Jackson libraries, in particular the dataformat, databind and datatype modules. Technical knowledge of the Jackson library will be helpful for maintenance of feature, but is not necessary.

However, understanding Jackson 's annotations will be needed in order to extend export and import functionality to cover new data fields or classes implemented in the future. They are explained here.

3.5.2. Csv Processing

All of the data processing needed for this feature can be found in the CsvUtil file; it is responsible for converting the data of Patient objects into a .csv friendly String format and vice versa. The actual reading and writing of .csv files is then done with existing FileUtil functionality.

The conversion of Patient objects to and from .csv formatted Strings are handled by the CsvMapper and CsvSchema classes implemented in dataformat.

  • The CsvMapper is responsible for managing custom configurations for the conversions. For instance, it can toggle headers with withHeader() and skip unknown fields with the IGNORE_UNKNOWN feature, both of which are used in the feature implementation.

  • The CsvSchema object is created by a configured CsvMapper object and an Object.class, in this case, Patient.class. It dictates the scheme for how data in each column of the .csv corresponds with the fields in the Object class. This schema can then be used to instantiate a reader or a writer object.

The following functions are implemented:

  1. CsvUtil#writePatientsToCsv(List<Patient>, String) — Write a list of Patient objects into a .csv file to the path at the specified pathString.

  2. CstUtil#readPatientsFromCsv(String) — Read a .csv file at the specified path indicated by pathString and return a corresponding list of Patient objects.

  3. importsContainDupes(List<Patient>) — Check if the given list contains duplicate patients, i.e. the list is not unique.

3.5.3. Csv Processing Design Considerations

  • Alternative 1 (current choice): Use Jackson 's dataformat.csv library.

    • Pros: Well integrated with the existing Jackson JSON parsing libraries. Tons of functions for CSV parsing.

    • Cons: Does not support Excel formats. Comprehensive library, recently updated but not well-documented. Hard to understand.

  • Alternative 2: Use other libraries, like Apache Commons CSV.

    • Pros: More features like Excel format parsing. Well documented.

    • Cons: Does not integrate well with the existing Jackson libraries used.

  • Alternative 3: Build your own parser!

    • Pros: Lightweight, implement only what is needed. Can be white box tested.

    • Cons: Lots of developer work to reinvent the wheel. Likely to be buggy as there are tons of edge cases to consider, due to many special characters.

3.5.4. Command Implementation

Command: app-export

The app-export command works by retrieving a list of Patient objects from the Model and passing it to CsvUtil to process and write into a .csv file. If provided with indexes, the app-export feature can selectively export the Patient objects that correspond to the specified indexes. To facilitate the selective export functionality, the following methods were implemented in Model and PatientBook.

  • Model#GetPatientsByIndexes(Set<Indexes>) — Retrieves and returns a list of Patient objects corresponding to the provided indexes from the PatientBook , if the indexes are valid.

  • PatientBook#GetPatientByIndex(Index) — Returns the Patient object corresponding to the specified index, if the index is valid.

If indexes are not specified in the command arguments, a list of all currently existing Patient objects will be retrieved with Model#getStagedPatientList().

The app-export command MUST be provided with a desired file name for the .csv file. The .csv file will be written to /exports/[FILENAME].csv. Existing files will NOT be overridden and thus the provided file name cannot already exist in /exports.

The following diagrams show how an export command works:

ExportSequenceDiagram
Figure 18. Export Sequence Diagram
ExportSequenceDiagramRef
Figure 19. Reference: get patient list
ExportSequenceDiagramRef2
Figure 20. Reference: write patients to csv file
ExportCommand Design Considerations
Aspect: File Overriding
  • Alternative 1 (current choice): Disallow overriding, file name provided must be new

    • Pros: Existing .csv files will not be accidentally overridden. Prevents potential loss of data.

    • Cons: Additional hassle for the user to delete files that they want to replace.

  • Alternative 2: Allow overriding

    • Pros: Conveniently replace existing, unused files.

    • Cons: May accidentally override and lose important data.

Aspect: Illegal Characters in Data Fields
  • Alternative 1 (current choice): Allow forbidden characters

    • Pros: Certain fields may be more accurately represented, i.e. addresses.

    • Cons: The exported csv file may be bugged in edge cases, i.e. have data in the wrong columns. Exported fields with forbidden characters may not be properly handled and escaped all of the time. More developer work to test around edge cases.

  • Alternative 2: Disallow forbidden characters

    • Pros: Exported .csv files are guaranteed to be in the correct format.

    • Cons: Data fields are restricted and cannot have commas, semi-colons, etc.

Import

The import commands work by reading a .csv file and converting it into a list of Patient objects by using CsvUtil. The list is then passed to Model. What happens next depends on which variant of import is called.

For both variants of the command, the imported list of patients CANNOT have any duplicates. This is ensured with CsvUtil#importsContainDupes(List<Patient>).

Command: app-import-replace

The Model will replace all existing Patient data in the PatientBook with the data of the new list of Patient objects. To do this, the following was implemented:

  • Model#replaceStagedPatientBook(List<Patient>) — Creates a new PatientBook object containing the Patient objects in the provided list. The old PatientBook stored in the Model is then replaced with the new PatientBook by calling Model#setStagedPatientBook(PatientBook).

Command app-import-merge

The Model will add all Patient data in the new list of Patient objects into the PatientBook. To do this, the following was implemented:

  • Model#hasAnyPatientInGivenList(List<Patient>) — Checks if the model contains any Patient in the given list of Patient objects.

  • Model#addPatients(List<Patient>) — Adds all Patient objects in the given list into the Model.

If the operation will result in duplicate Patient objects in the PatientBook, it will not be executed. This is checked by the function stated above, Model#hasAnyPatientInGivenList(List<Patient>).

Import Design Considerations
Aspect: Allowing Patient Overriding for app-import-merge
  • Alternative 1 (current choice): Disallow overriding.

    • Pros: Existing Patient data will not be accidentally overridden. No need to deal with potential merge conflicts.

    • Cons: User may have intended to use app-import-merge to update old data. More hassle for the user to delete old Patient data that they want to replace.

  • Alternative 2: In case of duplicates, replace old Patient data.

    • Pros: Conveniently update old Patient data.

    • Cons: May accidentally override and lose important data, though not a big deal with app-undo.

  • Alternative 3: Implement a flag to toggle overriding.

    • Pros: Best of both worlds.

    • Cons: More coding and debugging work.

3.7. Overview Of Implementation

The auto-complete mechanism extends NurseTraverse with 3 functionalities

  • Completing word in command box

  • Suggesting of words that are compatible with previous sub-commands

  • Guide for user to select from the various sub-commands

Given below is an example usage scenario of autocompletion

Step 1: When the user launches the application, an instance of the object, command and prefix autocomplete word list will be initialised in AutoCompleteStorage, which implements the following operations:

  • AutoCompleteWordStorage#getOListAllCommandWord() — returns the observable list of command words stored

  • AutoCompleteWordStorage#getOListAllPrefixWord() — returns the observable list of prefix words stored

  • AutoCompleteWordStorage#getOListAllObjectWord() — returns the observable list of object words stored

  • AutoCompleteWordStorage#generateOListAllIndexWord(String objectName) — generate an observable list of index words corresponding to previous object word when called

The auto-complete panel will be set to object list when user first start the application.

Step 2: Whenever user types a key in the command line, CommandBox will notify AutoCompletePanelManager to perform changes based on userinput

This will update the number of matched words with the current list and 3 sets of algorithms will be run

  • AutoCompleteListHandler#chooseList()
    → Based on the number of matched words(pre-processed in the AutoCompletePanelManager)
    → List will be chosen from AutoCompleteStorage

  • AutoCompleteListHandler#filterList()
    → The chosen list will then be filter to only words that are compatible with previous correctly typed full words.
    → This is done by iterating through all the matched word and checking if AutoCompleteWord has a list of associated words
    → If the AutoCompleteWord has a list of associated words, this method will attempt to match the associated words with the matched words while filter out all incompatible words in the chosen list in the process
    (Eg: prefix list will be filtered to only be compatible with “pat-add” user command, the words that come before prefix.)

  • AutoCompleteListHandler#updateList()
    → Update the filtered list to fit the last partially typed word
    → The last partially typed word in CommandBox will be checked against the filtered list
    → If the word in the filter list does not start with the last partially typed word, it will be filtered out
    (Eg: User typed “pat-ad”. “add” and “add-medcon” which are both in the current list will be suggested since they both starts with "ad".)

The following sequence and activity diagrams illustrates the general flow when user input is detected

Up key pressed
Shift key pressed
Any other key pressed
ref generating list

Shown below is the sequence diagram for the guided system when selecting suggestion word

GuideSystemSequenceDiagram

3.8. Design Considerations

Aspect: Communication between UiPart s

(CommandBox, AutoCompletePanel and ResultDisplay)

  • Alternative 1: Pass references of each UiParts components around

    • Pros: Simpler to call methods as required, code will be easier to understand and implementation will take less effort

    • Cons: Code will be harder to track since any component with reference to the UiPart can manipulate its property

  • Alternative 2(current choice): Implement facades and observer patterns

    • Pros: Code is easier to maintain

    • Cons: Less readability

3.9. Logic Design Considerations

Aspect: Method to suggest auto-complete words
  • Alternative 1: Have several lists. First word list, Second word list, etc

    • Pros: Minimal effort in parsing of command line input.

    • Cons: Auto-complete panel will be unorganised. Eg: both index 1 and prefix may be suggested instead of just all index or all prefix suggested.

  • Alternative 2: Suggest full commands such as pat-edit 2 n/ t/ .. and omit any list

    • Pros: Easy to implement. No parsing required.

    • Cons: Auto-complete panel will be chunky as all commands are suggested at the same time. User will not be guided in selecting which command to choose.

  • Alternative 3(current choice): Have several lists. Object/Command/Index/Prefix lists.

    • Pros: User will be guided. Very user friendly. Panel will be more organised as words are suggested in sets of the same type.

    • Cons: Parsing of command line input can be tedious.

3.10. UI Design Considerations

Aspect: Location of autocomplete panel
  • Alternative 1: Place it as a floating window

    • Pros: User is able to resize and can be moved around easily

    • Cons: A floating window may block out some of the UI of the application, and user will have to re-place the autocomplete panel every time the application starts

  • Alternative 2(current choice): Fix it permanently at a pre-set location

    • Pros: Simpler and user will get use to the its location easier since it is pre-set

    • Cons: Less customisable for user

3.11. Logging

We are using java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

  • The logging level can be controlled using the logLevel setting in the configuration file (See Section 3.12, “Configuration”)

  • The Logger for a class can be obtained using LogsCenter.getLogger(Class) which will log messages according to the specified logging level

  • Currently log messages are output through: Console and to a .log file.

Logging Levels

  • SEVERE : Critical problem detected which may possibly cause the termination of the application

  • WARNING : Can continue, but with caution

  • INFO : Information showing the noteworthy actions by the App

  • FINE : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size

3.12. Configuration

Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json).

4. Documentation

Refer to the guide here.

5. Testing

Refer to the guide here.

6. Dev Ops

Refer to the guide here.

Appendix A: Product Scope

Target user profile:

  • community nurse

  • needs to manage a significant number of homebound patients

  • has a need to manage patients’ profile efficiently

  • has flexibility in scheduling appointments

  • is reasonably comfortable using CLI apps

Value proposition: Makes managing patients’ data more orderly, efficient and tidy

Appendix B: User Stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *

Priority As a …​ I want to …​ So that I can…​

* * *

user

see the next-of-kin contact (address, name, phone) associated with each patient

inform them in case of any emergency situations

* * *

user

track the addresses of my patients

I will not forget them

* * *

user

keep track of the dosages of medications of my patients

I know exactly what they need to take

* * *

user

see my schedule of appointments for a day

I know when to go where

* * *

user

see all my patients’ medications in a list

I can pack my bags in advance of each trip

* * *

user

see the medical conditions of each patient so that

I know how to care for them

* * *

user

record details (these details can be broken down) about the visit (e.g. environmental conditions, patient’s symptoms, warning signs)

I can better monitor the patient’s situation

* * *

user

save my data automatically after each change

data doesn’t get lost if I forgot or am not able to save after a session

* *

user

take personal notes for each patient

I can take note of their personal quirks and needs

* *

user

use a todo list / checklist to keep track of what I have to do when I visit a patient

I won’t forget or miss out on any important tasks when I visit them

* *

user

I want to keep track of todos I have done

I will have documentations to fall back on if i need them

* *

user

have a manual for how to do certain tasks (e.g. clean wounds / first aid

I am better prepared in times of emergency

* *

user

have a manual on medical conditions

I am better prepared to advise patients about their medical condition

* *

user

keep a photo of each patient so I can

remember them better

* *

user

alert or send reminders to patients to take any prescribed medication so that

they take their medication on time and regularly

* *

user

set the level of importance to each todo

urgent matters regarding patients is taken priority of and can be taken care of first

* *

user

have a list of emergency contacts

I will be able to find them easily in times of emergency

* *

user

be able to organise patient’s paperwork (appointment letters for checkups)

my patients don’t miss their appointments

* *

user

have a notification system

I will be reminded of important matters to settle

* *

user

be able to import and export my app data patient-by-patient

I can share with other nurses or manager

* *

user

have an undo function

I don’t accidentally make irreversible changes due to my mistakes

* *

user

have a pin login system

my patients’ information will be kept private

* *

user

have my patients’ information encrypted

their information is kept private and secure

* *

user

be able to see all the patient’s information in a structured format

so that messy and complex details are presented to me in an organised manner

* *

user

have an autocomplete function

I can use the application more quickly without memorising all the commands of the application

* *

user

have a help function

I do not need to worry about memorising all the commands of the application

* *

user

assign each patient a priority number

I can keep track of who needs me the most

* *

user

hide private contact details by default

minimize chance of someone else seeing them by accident

*

user

solve the Travelling CN problem

I can save time when visiting my patients

*

user

sort my patient list by priority

I can keep in mind who to visit

*

user

keep track of each patient’s available times

I know when I can visit them

Appendix C: Use Cases

(For all use cases below, the System is the PatientBook and the Actor is the user, unless specified otherwise)

Use case: Add patient

MSS

  1. User requests to add a new patient to the list

  2. Application adds the patient to the list

    Use case ends.

Extensions

  • 1a. The list is empty.

    Use case ends.

  • 1a. The one or more given parameters are invalid.

    • 1a1. Application shows an error message.

      Use case ends.

Use case: Delete patient profile

MSS

  1. User requests to list persons

  2. Application shows a list of persons

  3. User requests to delete a specific patient in the list

  4. Application delete the specified patient profile

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The one or more given parameters are invalid.

    • 3a1. Application shows an error message.

      Use case resumes at step 2.

Use case: Adding an appointment

MSS

  1. User requests to list persons

  2. Application shows a list of persons

  3. User picks one patient from the list and add appointment date and time through command line

  4. Application adds user appointment

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The one or more given parameters are invalid.

    • 3a1. Application shows an error message.

      Use case resumes at step 2.

Appendix D: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 11 or above installed

  2. Should be able to hold up to 50 patient profile without any noticeable performance issue

  3. Users should be able to understand how to use the system easily

  4. 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

  5. GUI should be straightforward and clear

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Private contact detail

A contact detail that is not meant to be shared with others

Appendix F: Instructions for Manual Testing

Given below are instructions to test the app manually.

These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.

F.1. Launch and Shutdown

  1. Initial launch

    1. Download the jar file and copy into an empty folder

    2. Double-click the jar file
      Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.

  2. Saving window preferences

    1. Resize the window to an optimum size. Move the window to a different location. Close the window.

    2. Re-launch the app by double-clicking the jar file.
      Expected: The most recent window size and location is retained.

F.2. Deleting a patient

  1. Deleting a patient while all patients are listed

    1. Prerequisites: List all patients using the pat-list command. Multiple patients in the list.

    2. Test case: pat-delete 1
      Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.

    3. Test case: pat-delete 0
      Expected: No patient is deleted. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect delete commands to try: pat-delete, pat-delete x (where x is larger than the list size)
      Expected: Similar to previous.

F.3. Updating a patient’s Visit Todos

  1. Updating a patient’s Visit Todos. Note that this will not update the patient’s visit tasks in an ongoing visit. For more information of the distinction between visit todos and visit tasks, refer to Visit Tutorials in the User Guide.

    1. Test case: pat-edit 1 vt/Check Blood Pressure
      Expected: Patient is updated with the new Visit Todo and this is reflected in the status message.

    2. Test case: pat-edit 1 vt/
      Expected: Patient is updated with no Visit Todos.

F.4. Starting a Visit for a patient

  1. Starting a visit for a patient.

    1. Prerequisite: There is no ongoing visit.

    2. Test case: visit-start 1
      Expected: An ongoing visit is begun.

    3. Prerequisite: There is an ongoing visit.

    4. Test case: visit-start 1
      Expected: No visit is begun. An error message will appear in the status message informing user that there is already an ongoing visit.

F.5. Updating an ongoing visit

  1. Updating an ongoing visit.

    1. Prerequisite: There is an ongoing visit, and this visit has at least one visit task.

    2. Test case: visit-now-update f/1 r/Patient is healthy. d/1 140/90mmHg
      Expected: Visit is updated. The first visit task is marked as finished with a detail of ""140/90mmHg" and the visit has the remark "Patient is healthy.". This is reflected on the Current Visit tab and in the status message.

    3. Prerequisite: There is no ongoing visit.

    4. Test case: visit-now-update [any argument]
      Expected: No visit is updated. An error message will appear in the status message.

Note: If the format of visit-now-update arguments are wrong, the error message will prompt about the format first, before it prompts on whether or not there is an ongoing visit.

F.6. Cancelling a Visit for a patient

  1. Cancelling a visit for a patient.

    1. Prerequisite: There is an ongoing visit.

    2. Test case: visit-cancel
      Expected: The ongoing visit is cancelled.

    3. Prerequisite: There is no ongoing visit.

    4. Test case: visit-cancel
      Expected: No visit is cancelled. An error message will appear in the status message.

F.7. Finishing a Visit for a patient

  1. Finishing a visit for a patient.

    1. Prerequisite: There is an ongoing visit.

    2. Test case: visit-end
      Expected: The ongoing visit is finished. The end date time should be indicated in the visit if you check it in the patient’s list of Visits.

    3. Prerequisite: There is no ongoing visit.

    4. Test case: visit-end
      Expected: No visit is finished. An error message will appear in the status message.

F.8. Adding an appointment

  1. Adding an appointment while all appointments are listed

    1. Prerequisites:

      1. List all appointments using the appt-list command. Multiple appointments in the list.

      2. Ensure all the suggested date times below don’t already exist in the appointment book. If they do, try with start and end date times that don’t exist in the appointment list to get the expected results.

    2. Test case: appt-add p/1 sdt/01-01-2020 1000 edt/01-01-2020 1200
      Expected: Appointment is added to the list (based on its start date and time as the list is sorted by that) with first patient’s details from the list of patients and the input start and end date times. Details of the added appointment shown in the status message. Timestamp in the status bar is updated.

    3. Test case: appt-add p/1 sdt/14-02-2020 2000 edt/14-02-2020 2100 rmon/4
      Expected: Appointment is added to the list with first patient’s details from the list of patients, the input start and end date times, and recurring frequency of 4 months. The added appointment will be marked recurring as well. Details of the added appointment shown in the status message. Timestamp in the status bar is updated.

    4. Test case (two commands): appt-add p/1 sdt/12-02-2020 2000 edt/12-02-2020 2100 ryr/1
      Followed by:
      appt-add p/1 sdt/12-02-2020 2000 edt/12-02-2020 2100 rmon/1
      Expected: No appointment is added. Error details shown in the status message. Status bar remains the same.

    5. Test case (two commands): appt-add p/1 sdt/10-02-2020 2000 edt/12-02-2020 2100
      Followed by:
      appt-add p/1 sdt/10-02-2020 2000 edt/12-02-2020 2100
      Expected: No appointment is added. Error details shown in the status message. Status bar remains the same.

    6. Other incorrect appointment add commands to try: appt-add, appt-delete p/x (where x is larger than the patient list size)
      Expected: Similar to previous.

F.9. Editing an appointment

  1. Editing an appointment while all appointments are listed

    1. Prerequisites:

      1. List all appointments using the appt-list command. Multiple appointments in the list.

      2. Ensure all the suggested date times below don’t already exist in the appointment book. If they do, try with start and end date times that don’t exist in the appointment list to get the expected results.

    2. Test case: appt-edit 1 p/5 sdt/01-01-2020 1000 edt/01-01-2020 1200
      Expected: First appointment is edited in the list to fifth patient’s details from the list of patients and the input start and end date times as mentioned. Details of the edited appointment shown in the status message. Timestamp in the status bar is updated.

    3. Test case: appt-edit 1 rmon/3
      Expected: First appointment is edited in the list to recur by 3 months. If the appointment was previously not recurring, it will be tagged as recurring. Details of the edited appointment shown in the status message. Timestamp in the status bar is updated.

    4. Test case: appt-edit 3 p/0
      Expected: No appointment is edited. Error details shown in the status message. Status bar remains the same.

    5. Other incorrect appointment edit commands to try: appt-edit, appt-edit x (where x is larger than the appointment list size)
      Expected: Similar to previous.

F.10. Deleting a non-recurring appointment

  1. Deleting a non-recurring appointment while all appointments are listed

    1. Prerequisites:

      1. List all appointments using the appt-list command. Multiple appointments in the list.

      2. xth appointment in the appointment list should be a non-recurring appointment, where x is a positive integer.

    2. Test case: appt-delete x
      Expected: xth appointment is deleted from the list. Details of the deleted appointment shown in the status message. Timestamp in the status bar is updated.

    3. Test case: appt-delete 0
      Expected: No appointment is deleted. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect non-recurring appointment delete commands to try: appt-delete, appt-delete x (where x is larger than the appointment list size)
      Expected: Similar to previous.

F.11. Deleting a recurring appointment

  1. Deleting a recurring appointment while all appointments are listed

    1. Prerequisites:

      1. List all appointments using the appt-list command. Multiple appointments in the list.

      2. xth appointment in the appointment list should be a recurring appointment.

    2. Test case: appt-delete x
      Expected: xth appointment is deleted from the list and the next appointment from xth appointment being recurred is gotten and added to the list. Details of the deleted appointment shown in the status message. Timestamp in the status bar is updated.

    3. Test case: appt-delete 0
      Expected: No appointment is deleted. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect recurring appointment delete commands to try: appt-delete, appt-delete x (where x is larger than the appointment list size)
      Expected: Similar to previous.

F.12. Deleting an appointment permanently

  1. Deleting an appointment permanently while all appointments are listed

    1. Prerequisites: List all appointments using the appt-list command. Multiple appointments in the list.

    2. Test case: appt-delete-permanent 1
      Expected: First appointment is deleted from the list. Details of the deleted appointment shown in the status message. Timestamp in the status bar is updated.

    3. Test case: appt-delete-permanent 0
      Expected: No appointment is deleted. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect appointment permanent delete commands to try: appt-delete-permanent, appt-delete-permanent x (where x is larger than the appointment list size)
      Expected: Similar to previous.

F.13. Changing of autocomplete suggestion list

  1. Changing list from object list to command list.

    1. Test case: Type "pat-" into empty command box
      Expected: The suggestion list is changed from object list to command list

  2. Changing list from command list to prefix list.

    1. Test case: Type "pat-add" into empty command box
      Expected: The suggestion list is changed from command list to prefix list

  3. Changing list from command list to index list.

    1. Prerequisite: At least 1 patient registered in application

    2. Test case: Type "pat-edit" into empty command box
      Expected: The suggestion list is changed from command list to index list

  4. Changing list from index list to prefix list.

    1. Prerequisite: At least 1 patient registered in application

    2. Test case: Type "pat-edit 1" into empty command box
      Expected: The suggestion list is changed from index list to prefix list

F.14. Updating of current autocomplete suggestion list

  1. Updating the object list.

    1. Test case: Type "ap" into empty command box
      Expected: The suggestion list should only contain "appt-" and "app-" now

F.15. Exporting data

  1. Export patient data into a CSV file

    1. Prerequisite: There are existing patients.

    2. Test case: app-export n/testcase1
      Expected: All patient data written to ./exports/testcase1.csv.

    3. Prerequisite: There are existing patients at indexes 1 and 3.

    4. Test case: app-export n/testcase2 i/1 i/3
      Expected: Data of patients at index 1 and 3 written to ./exports/testcase2.csv.

F.16. Importing data

  1. Import patient data from a CSV file

    1. Prerequisite: ./imports/testcase3.csv exists and contains some patient data in the correct format.

    2. Test case: app-import-replace n/testcase3
      Expected: All patient data is replaced with patients from testcase3.csv.

    3. Prerequisite: ./imports/testcase4.csv exists and contains only new patient data in the correct format.

    4. Test case: app-import-merge n/testcase3
      Expected: All patients in testcase4.csv added into the app.

F.17. Viewing the command history:

  1. Viewing the command history.

    1. Prerequisite: None.

      1. Test case: pat-list
        Expected: No change to the history panel.

    2. Prerequisites: List all patients using the pat-list command. At least one patient in the list. No ongoing visit.

      1. Test case: pat-delete 1
        Expected: pat-delete 1 appears in the history panel at the bottom of the list with the greatest index number.

F.18. Undoing a command:

  1. Undoing a command.

    1. Prerequisite: There are commands in the history.

      1. Test case: app-undo
        Expected: The previous command in the history is undone.

      2. Test case: app-undo 1
        Expected: All commands in the history are undone.

    2. Prerequisite: There are no commands in the history.

      1. Test case: app-undo
        Expected: No command is undone. An error message appears in the status box.

      2. Test case: app-undo 1
        Expected: No command is undone. An error message appears in the status box.

F.19. Redoing a command:

  1. Redoing a command.

    1. Prerequisite: app-undo is the last successful data-modifying command.

      1. Test case: app-redo
        Expected: The undone command is redone.

    2. Prerequisite: app-undo is not the last successful data-modifying command.

      1. Test case: app-redo
        Expected: No command is redone. An error message appears in the status box.