diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..9c081d7b94 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "workbench.editor.defaultBinaryEditor": "default" +} \ No newline at end of file diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..a2d87b6b88 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: ClinicEase + diff --git a/README.md b/README.md index f3d0bded12..b6d564ce16 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# ClinicEase This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. diff --git a/build.gradle b/build.gradle index ea82051fab..4686c26b8a 100644 --- a/build.gradle +++ b/build.gradle @@ -29,11 +29,11 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("ClinicEase") } shadowJar { - archiveBaseName.set("duke") + archiveBaseName.set("ClinicEase") archiveClassifier.set("") } @@ -43,4 +43,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/data/appointment_data.txt b/data/appointment_data.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/patient_data.txt b/data/patient_data.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/data/prescription_data.txt b/data/prescription_data.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..408ed9011c 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,9 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +| Display | Name | Github Profile | Portfolio | +|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:--------------------------------:| +| ![](https://via.placeholder.com/100.png?text=Photo) | Basudeb Chakraborty | [Github](https://github.com/Basudeb2005) | [Portfolio](team/Basudeb2005.md) | +| ![](https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.detik.com%2Fedu%2Fedutainment%2Fd-6761377%2Fblak-blakan-judha-siswa-sma-pradita-dirgantara-yang-diterima-11-kampus-luar-negeri&psig=AOvVaw1BQ3wU1pFhYGxi0EuDmLOq&ust=1741317092826000&source=images&cd=vfe&opi=89978449&ved=0CBQQjRxqFwoTCLiFj-m99IsDFQAAAAAdAAAAABAE) | Judha Hoka Wishika | [GitHub](https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.detik.com%2Fedu%2Fedutainment%2Fd-6761377%2Fblak-blakan-judha-siswa-sma-pradita-dirgantara-yang-diterima-11-kampus-luar-negeri&psig=AOvVaw1BQ3wU1pFhYGxi0EuDmLOq&ust=1741317092826000&source=images&cd=vfe&opi=89978449&ved=0CBQQjRxqFwoTCLiFj-m99IsDFQAAAAAdAAAAABAE) | [Portfolio](team/judhoka.md) | +| ![](https://via.placeholder.com/100.png?text=Photo) | Yukuan | [Github](https://github.com/jyukuan) | [Portfolio](team/jyukuan.md) | +| ![](https://via.placeholder.com/100.png?text=Photo) | Dylan Chua | [Github](https://github.com/dylancmznus) | [Portfolio](team/dylancmznus.md) | +| ![](https://via.placeholder.com/100.png?text=Photo) | Chan Wen Yee | [Github](https://github.com/chwenyee) | [Portfolio](team/chwenyee.md) | diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..0cf26f8262 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,1674 @@ # Developer Guide -## Acknowledgements +- [Acknowledgement](#acknowledgements) +- [Design](#design-) + - [Architecture](#architecture) + - [Command Component](#command-component) + - [Ui Component](#ui-component) + - [Main Component](#main-component) + - [Parser Component](#parser-component) + - [Manager Component](#manager-component) + - [Objects Component](#objects-component) + - [Storage Component](#storage-component) +- [Implementation](#implementation) + - [View Patient Feature](#view-patient-feature) + - [Add Patient Feature](#add-patient-feature) + - [Delete Patient Feature](#delete-patient-feature) + - [Add/Delete Appointment Feature](#adddelete-appointment-feature) + - [Storing Medical History Feature](#storing-medical-history-feature) + - [Presciption Management Feature](#prescription-management-feature) +- [Appendix: Requirements](#appendix-requirements) + - [Product Scope](#product-scope) + - [User Stories](#user-stories) + - [Use Cases](#use-cases) + - [Non-Functional Requirements](#non-functional-requirements) + - [Glossary](#glossary) +- [Appendix: Instructions for Manual Testing](#appendix-instructions-for-manual-testing) -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} -## Design & implementation +--- -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +### Acknowledgements +We would like to acknowledge the following for their support and inspiration during the development of ClinicEase: + +- The teaching team of [CS2113] for providing guidance, sample code, and foundational project architecture. +- Open-source libraries and tools from the Java SDK. +- Online documentation and tutorials that supported implementation and testing efforts. + +All code was developed by the team unless otherwise specified. + +--- + +## Design + +### Architecture + +![Architecture Diagram](diagrams/ArchitectureDiagram.png) + +The **Architecture Diagram** given above explains the high-level design of the ClinicEase application. The diagram shows the components of the system organized by packages and how they interact with each other. + +Given below is a quick overview of each component and how they interact with each other: + +
+ +### Main components of the architecture + +The application consists of the following components: + +* **Miscellaneous**: Contains the user interface and parser components. + * **UI**: Handles user interaction, displays information to the user, and reads commands from the console. + * **Parser**: Processes user input strings and converts them into appropriate Command objects. + +* **Main**: The entry point of the application and acts as the orchestrator that connects all other components + +* **Command**: Follows the Command pattern to implement various operations. + * **Command (Main)**: An abstract class that all specific commands extend. + * **Various Commands**: Specific command classes (AddPatientCommand, DeletePatientCommand, etc.) that implement different operations. + +* **Manager**: The central coordinator that manages collections of entities and implements business logic. + +* **Object**: Patient, Appointment, and Prescription classes that represent domain objects. + +* **Storage**: Saves and loads data to/from specific data files. + +### How the architecture components interact with each other + +The **Sequence Diagram** below shows how the components interact for the scenario where the user executes a `delete-patient` command: + +![Sequence Diagram](diagrams/ArchitectureSequenceDiagram.png) + +The typical flow of execution in the system is as follows: + +1. User enters a command through the UI +2. UI passes the command string to the Parser +3. Parser analyzes the command and creates the appropriate Command object +4. Command object executes the operation, interacting with the ManagementSystem +5. ManagementSystem updates its internal state and may request the Storage to save changes +6. Results are displayed to the user through the UI + +This architecture follows several design principles: + +1. **Single Responsibility Principle**: Each component has a specific responsibility +2. **Separation of Concerns**: UI logic is separated from business logic which is separated from data persistence logic +3. **Command Pattern**: Commands encapsulate actions and provide a uniform interface for execution +4. **Entity-Control Separation**: Entity classes (Patient, Appointment, Prescription) are separate from the controlling ManagementSystem that operates on them + +
+ +--- + +## Command Component +**API** : [Command classes](https://github.com/AY2425S2-CS2113-T11b-4/tp/tree/master/src/main/java/command) + +![Command Component](diagrams/Command.png) + + +### Overview + +The `Command` component is responsible for **handling and executing user commands** in ClinicEase. It defines a common interface (`Command`) for all commands and organizes the various command classes (e.g., `AddAppointmentCommand`, `DeletePatientCommand`) that operate on the `ManagementSystem` and interact with the `Ui` to provide application functionality. + +### Responsibilities + +1. **Encapsulate Command Logic**: Each command class holds the data or parameters needed to carry out a specific user request (e.g., adding a new appointment, deleting a patient). +2. **Execute on System**: Using a uniform `execute()` interface, commands interact with the `ManagementSystem` to perform the requested action. +3. **Provide Exit Control**: Certain commands can signal the application to terminate (e.g., `ExitCommand`), by overriding `isExit()` to return `true`. + + +### Key Features + +- **Abstract Base Class** + `Command` is declared as an abstract class, defining two key methods: + - `execute(manager: ManagementSystem, ui: Ui) : void` – where the command’s logic is implemented. + - `isExit() : boolean` – indicates if the command should terminate the main program loop. + +- **Multiple Subclasses** + Each subclass, such as `AddPatientCommand` or `FindAppointmentCommand`, implements the `execute()` method to fulfill a specific function (adding a patient, finding an appointment, etc.). + +- **Command Pattern** + This design follows a simplified [Command Pattern](https://en.wikipedia.org/wiki/Command_pattern), enabling a clear separation of concerns between **input parsing**, **system logic**, and **UI presentation**. + + +### Structure + +1. **Single Abstract Class** + `Command` resides at the top of the hierarchy. + +2. **Subclasses** + - **AddAppointmentCommand** — adds a new appointment to the system. + - **DeletePatientCommand** — removes a patient by NRIC. + - **SortAppointmentCommand** — sorts appointments by date or ID. + - *(... other commands like `EditPatientCommand`, `ViewPrescriptionCommand`, etc.)* + +3. **Uniform Execution** + All command classes share the same `execute()` signature but implement distinct behaviors. + + +### Dependencies + +- **`ManagementSystem`**: Each command calls methods from `ManagementSystem` to manipulate the core data (patients, appointments, prescriptions). +- **`Ui`**: Commands display success/failure messages and prompt user outputs via the `Ui` component. +- **`Parser`**: Typically, commands themselves do not parse raw input strings. Instead, the `Parser` constructs a command object with the necessary parameters before execution. +- **`Storage`**: Some commands (e.g., `AddPatientCommand`) indirectly trigger save operations, relying on `ManagementSystem` which delegates to `Storage` to persist changes. + + +### Design Considerations + +- **Extensibility** + Adding a new user action only requires creating a new `Command` subclass and updating `Parser` to recognize the new keyword(s). + +- **Error Handling** + Commands may throw or handle exceptions such as `PatientNotFoundException` or `UnloadedStorageException`, ensuring robust operation even if data is invalid or storage is unavailable. + +- **Single Responsibility** + Each command focuses on exactly one task (e.g., marking an appointment). This keeps classes small, cohesive, and easier to test or maintain. + +### Remarks + +- Commands are **stateless** aside from storing the **parameters** needed for execution (e.g., an appointment ID to be deleted). +- The **flow** is typically: + 1. `Parser` reads user input and creates an appropriate `Command` instance. + 2. The main loop calls `command.execute(manager, ui)`. + 3. If `command.isExit()` is `true`, the application terminates. + +- This approach **decouples** user input parsing from application logic, making the system easier to extend without modifying existing commands. + +> **Note:** The `Command` classes rely on the `Parser` to supply valid parameters. If parsing fails, `Parser` throws appropriate exceptions before the `Command` is even constructed. +--- + +## UI Component +**API**: [`Ui.java`](https://github.com/AY2425S2-CS2113-T11b-4/tp/blob/master/src/main/java/miscellaneous/Ui.java) + +![UI Component](diagrams/uiComponent.png) + +--- + +### Overview + +The `Ui` component manages **all user-facing interactions** in ClinicEase. It is responsible for reading raw user input, printing messages, displaying command results, and showing relevant errors or validations. By centralising all communication with the user, it ensures a consistent and clean user experience. + +--- + +### Responsibilities + +1. **User Communication**: Display outputs from commands, such as lists of patients, appointment confirmations, and system messages (welcome, exit, help, etc.). +2. **Input Handling**: Use `Scanner` to accept input from the command line and return it to the main program loop. +3. **Error Presentation**: Show descriptive error and validation messages to guide user actions. + +--- + +### Key Features + +- **Central Input Reader** + - Uses a single `Scanner` to read commands from standard input (`System.in`). + - Exposes a `readCommand()` method to fetch raw user input. + +- **Output Formatter** + Provides structured, readable messages for: + - Patient operations (add, delete, view, list). + - Appointment operations (add, mark, unmark, list). + - System messages like welcome, goodbye, and help. + +- **Error Display** + Handles invalid input gracefully via methods like: + - `showError(String message)` + - `showInvalidCommand(String command)` + - `showIndexOutOfBoundsError(...)` + +- **Consistent User Interface** + All outputs use standard formatting (icons, line dividers, spacing) to maintain visual consistency and improve usability. + +--- + +### Structure + +1. **Input** + - `readCommand()` — prompts and captures user input via CLI. + +2. **General System Messages** + - `showWelcome()`, `showGoodbye()`, `showHelp()`, `showDivider()` + +3. **Patient Display** + - `showPatientAdded(Patient)` + - `showPatientDeleted(Patient)` + - `showPatientList(List)` + - `showPatientDetails(Patient)` + +4. **Appointment Display** + - `showAppointmentAdded(Appointment)` + - `showAppointmentMarked(Appointment)` + - `showAppointmentUnmarked(Appointment)` + - `showAppointmentList(List)` + +5. **Error and Validation** + - `showError(String)` + - `showInvalidCommand(String)` + - `showInvalidInputFormat(String)` + - `showIndexOutOfBoundsError(String, int, int)` + +--- + +### Dependencies + +- **`Patient` and `Appointment` Classes** + Used to format and display relevant model information in a human-readable format. + +- **`Command` Classes** + Call various UI methods to output results of command execution. + +- **`Main` Loop / Driver Class** + Calls `readCommand()` to get user input and dispatch commands. + +--- + +### Design Considerations + +- **Separation of Concerns** + UI strictly handles display and input, not business logic. This modularity improves maintainability and makes the system testable. + +- **Consistency and Usability** + Formatting styles (e.g., emojis/icons, dividers) improve user experience by clearly delineating different types of output. + +- **Scalability** + Designed to easily add new output functions (`showX`) when new features or commands are introduced. + +- **Extensibility** + Could be abstracted into an interface (`IUi`) to allow swapping between CLI, GUI (e.g. JavaFX), or web UI in future iterations. + +--- + +### Remarks + +- The UI component is **stateless**, meaning it does not store application data — it only serves as a medium to **interact** with users. +- It ensures a **clear communication loop**: + 1. `Parser` processes input → + 2. `Command` executes logic → + 3. `Ui` formats and displays the result. +- Centralising all UI output ensures messages are consistent across commands and improves the professionalism and polish of the CLI application. +- Future improvements could include: + - Theming/styling for different output types + - Logging system output to a file via a `Logger` + - Extending to support localisation/internationalisation +--- + +--- +### Main component +**API**: [`ClinicEase.java`](https://github.com/AY2425S2-CS2113-T11b-4/tp/blob/master/src/main/java/ClinicEase.java) + +![Main Component](diagrams/mainComponent.png) + +The `Main` component is the **entry point** of the application. + +--- +#### Responsibilities + +- Initializes core components: `Ui`, `Storage`, and `ManagementSystem`. +- Loads previously saved patient, appointment, and prescription data using `Storage`. +- Runs the main input loop by: + - Reading commands via `Ui` + - Parsing them into `Command` objects via `Parser` + - Executing commands with access to `ManagementSystem` and `Ui` +- Handles exceptions gracefully and displays errors to the user when needed. + +> While not a logic-heavy component itself, `Main` serves as the **coordinator** that ties together UI, command parsing, logic execution, and data storage. + +
+ +--- + +### Parser Component +**API** : [`Parser.java`](https://github.com/AY2425S2-CS2113-T11b-4/tp/blob/master/src/main/java/miscellaneous/Parser.java) + +#### Overview of the `Parser` Component +![parser-class-diagram](diagrams/parserClassDiagram.png) + +--- +- **Parser:** + - **Public API:** `parse(input)` + + - **Helper Methods:** `parseAddPatient()`, `parseAddAppointment()`, etc. + - These methods extract and validate input (using extractValue()), then return domain objects (Patient, Appointment, etc.). + + - **Utility Method:** `extractValue()` (reusable parameter extractor). + +--- +- **Command:** + - **Abstract Superclass:** Defines `execute()` and `isExit()` for all commands. + - **Subclasses:** `AddPatientCommand`, `DeleteAppointmentCommand`, etc. + - Each accepts specific data (e.g., `AddAppointmentCommand` takes an `Appointment`). + +--- +The sequence diagram below illustrates the interactions within the `Parser` component, taking parse(`add-appointment ic/S1234567D dt/2025-04-05 t/1400 dsc/Consultation`) +as example: +![parser-sequence-diagram](diagrams/parserSequence.png) + +> This sequence diagram only focus on the interactions within the `Parser` component, the other components +> such as `Ui`, `ManagementSystem` and `Storage` are omitted. + +--- +How the `Parser` component works: +1. When `Parser` is called upon to parse a user command, `Parser` splits the input to identify the command type (e.g. `add-appointment`). + A `switch-case` delegates to the appropriate helper method: +```` +public static Command parse(String userInput) throws InvalidInputFormatException, UnknownCommandException { + //... + String[] parts = userInput.split(" ", 2); + String commandWord = parts[0].toLowerCase(); + switch (commandWord) { + //... + case "add-appointment": + return new AddAppointmentCommand(parseAddAppointment(userInput)); + //... +```` + +2. The helper method (e.g., `parseAddAppointment()`) uses `extractValue()` to: + - Extract parameters (`ic/`, `dt/`, `t/`, `dsc/`) from the raw user input + - Validate formats (e.g., NRIC format, date parsing) + +3. The `Appointment` object will be constructed using extracted data. Then, `AddAppointmentCommand` + object is created, it will be returned to `ClinicEase` for further operations. + +
+ + +--- + +### Manager Component + +**API**: [`ManagementSystem.java`](https://github.com/AY2425S2-CS2113-T11b-4/tp/blob/master/src/main/java/manager/ManagementSystem.java) + +![Manager Component](diagrams/managerComponent.png) + +The `ManagementSystem` class in manager component acts as the central coordinator, responsible for managing features for **Patient**, **Appointment**, and **Prescription** entities. It provides methods to add, remove, and retrieve these objects, ensuring business logic is applied and maintaining the integrity of the system’s data. + +--- +#### Responsibilities + +- Manages instances of **Patient**, **Appointment**, and **Prescription** objects. +- Provides centralized methods for adding, removing, and retrieving from **Patients**, **Appointments**, and **Prescriptions**. +- Links **Appointments** and **Prescriptions** to their respective **Patients**. +- Ensures consistency of data, validating changes before committing them to the system. +- Handles business logic related to managing patients, their appointments, and prescriptions. + +--- +#### Key Features + +- Provides **methods** for managing **Patients**, **Appointments**, and **Prescriptions**. +- Ensures relationships between **Patients**, **Appointments**, and **Prescriptions** are properly maintained. +- Handles validation logic to ensure that the data is accurate and consistent (e.g., cannot add duplicate appointments or prescriptions). +- Can interact with **Storage** to persist changes made to patients, appointments, and prescriptions. + +--- +#### Structure + +- The core functionality is provided through a **single class** (ManagementSystem), where all methods for interacting with the system’s data are defined. +- Utilizes **arraylists** to store and manage **Patient**, **Appointment**, and **Prescription** objects. +- The **ManagementSystem** class contains methods like `addPatient()`, `removePatient()`, `getPatient()`, etc., and primarily serve to manage the logic behind those operations. +--- + +--- + +### Objects Component + +![Object Component](diagrams/objectComponent.png) + + +The **Objects** component defines the objects used by the **Manager** component, which are **Patient**, **Appointment**, and **Prescription**. These objects encompass the data and behavior specific to their respective classes. + + +#### Patient Class + +**API**: [`Patient.java`](https://github.com/AY2425S2-CS2113-T11b-4/tp/blob/master/src/main/java/manager/Patient.java) + +The `Patient` class represents a patient, including personal details, appointments, and prescriptions. + +##### Responsibilities + +- Encapsulates the **patient’s personal data** (e.g., name, contact information). +- Manages associated **appointments** and **prescriptions** for the patient. + +##### Key Features + +- Stores **personal details** like name, gender, and contact information. +- Can hold **appointments** and **prescriptions** for the patient. +- Provides methods for managing appointments and prescriptions directly tied to the patient. + + +#### Appointment Class + +**API**: [`Appointment.java`](https://github.com/AY2425S2-CS2113-T11b-4/tp/blob/master/src/main/java/manager/Appointment.java) + +The `Appointment` class represents an appointment, typically linked to a patient and a date and time. It contains details such as the appointment time, description and status. + +##### Responsibilities + +- Represents an **appointment** between a **patient** and a **doctor**. +- Stores information about the **date**, **time**, and **status** of the appointment. +- Allows updating the **status** of the appointment as completed. + +##### Key Features + +- Holds **appointment-related data**, including date, time, and status. +- Links to a **Patient** and optionally to a **Doctor**. +- Provides functionality to mark it as done. + +##### Dependencies + +- **Model**: Dependent on the **Patient** class for representing relationships with appointments. +- **ManagementSystem**: The **ManagementSystem** interacts with this class to manage appointments for patients. + +--- + +#### Prescription Class + +**API**: [`Prescription.java`](https://github.com/AY2425S2-CS2113-T11b-4/tp/blob/master/src/main/java/manager/Prescription.java) + +The `Prescription` class represents a prescription, typically linked to a patient and a doctor. It contains the prescribed medications and dosage instructions. + +##### Responsibilities + +- Represents a **prescription** issued by a **doctor** for a **patient**. +- Stores information about **medicines**, **dosage**, and **doctor notes**. +- Allows updates to the **dosage** or addition of new medications. + +##### Key Features + +- Holds **prescription-related data**, including medicine names, dosage, and additional instructions. +- Links to a **Patient** to ensure the prescription is tied to the correct patient. +- Provides methods to update the **dosage** or add new medicines. + +##### Dependencies + +- **Model**: Dependent on the **Patient** class to represent which patient the prescription is associated with. +- **ManagementSystem**: The **ManagementSystem** interacts with this class to manage prescriptions and ensure they are associated with the correct patient. + +--- + +## Storage Component +**API**: [`Storage.java`](https://github.com/AY2425S2-CS2113-T11b-4/tp/blob/master/src/main/java/storage/Storage.java) + +![Storage Component](diagrams/storageComponent.png) + + +### Overview + +The `Storage` component is responsible for reading from and writing to the file system to ensure **data persistence** in ClinicEase. It handles loading and saving of patients, appointments, and prescriptions, and also generates HTML representations of prescription data when required. + + +### Responsibilities + +- Save and load **patients** from `patient_data.txt`. +- Save and load **appointments** from `appointment_data.txt`. +- Save and load **prescriptions** from `prescription_data.txt`. +- Generate HTML files for prescriptions into a `/prescriptions/` directory. +- Ensure data is correctly formatted and persists across application runs. + + +### Key Features + +- **Separate Files for Each Data Type** + Each model object is saved in a different file for modularity and easier debugging. + +- **Static Utility Methods** + Methods like `savePatients()`, `loadAppointments()`, and `savePrescriptions()` are static and utility-based, allowing them to be called globally. + +- **Data Linking** + When appointments are loaded, they are automatically linked to their respective patients using methods from `ManagementSystem`. + +- **HTML Generation** + Prescription objects are exported into readable HTML files, supporting offline viewing or printing. + +- **Path Initialization** + The `Storage` constructor accepts a directory path and configures the correct file paths. It must be called before any static operations. + + +### Structure + +- The `Storage` class contains: + - A constructor `Storage(String directory)` to initialize the storage directory and file paths. + - Static methods: + - `savePatients(List)` + - `loadPatients()` + - `saveAppointments(List)` + - `loadAppointments(ManagementSystem)` + - `savePrescriptions(List)` + - `loadPrescriptions()` + - `exportPrescriptionToHtml(Prescription)` + + +### Dependencies + +- **Model Classes**: + - `Patient`: For reading/writing patient data. + - `Appointment`: For loading and linking to patients. + - `Prescription`: For saving and generating HTML files. + +- **ManagementSystem**: + Required to correctly link appointments to existing patients upon loading. + +- **Parser**: + Parses raw text from files and reconstructs `Patient`, `Appointment`, and `Prescription` objects. + +- **Commons / Utils**: + May use constants or helper functions for formatting or file handling. + + +### Design Considerations + +- **Separation of Concerns** + The `Storage` class is solely responsible for persistence. It does not contain any business logic related to how data is used. + +- **Safety Checks** + Throws `UnloadedStorageException` if static methods are called before proper initialisation via the constructor. + +- **Portability** + Allows flexible directory configuration via constructor, making it easy to adapt file locations for testing or deployment. + +- **Extensibility** + New file-based data (e.g., billing records) can be added without affecting the current architecture — simply add new load/save methods. + + +### Remarks + +- `Storage` should be **initialized once** using its constructor before any read/write operations are performed. +- Data is stored in a human-readable plain text format to support debugging and versioning. +- HTML generation for prescriptions can be enhanced in future with CSS templates or PDF export support. +- File structure and save formats are intentionally kept simple for ease of testing and transparency. + +--- + +
+ +## Implementation + +### View patient feature + +The 'view-patient' feature allows the user to retrieve and view the personal details of a specified patient. + +**Step 1.** The user launches the application for the first time. +- The `ClinicEase` will be initialized and load stored patient data. +- The patient's details are now ready to view. + +**Step 2.** The user executes `view-patient ic/S1234567D` command to view the patient's details. +- This command lets `ClinicEase` read the user input through the `UI` and pass it to `Parser`. +- The `Parser` class determines that the command is `view-patient` and creates a `ViewPatientCommand` object. + +> **Note:** +> If the input does not match the expected format, an `InvalidInputFormatException` is thrown. Hence, the patient's details will not be retrieved. + +**Step 3.** The system calls `execute()` method in `ViewPatientCommand`. +- This class calls for the patient list in `ManagementSystem`. +- The system checks if the patient exists in the list: + - If the NRIC exists, the system retrieves the patient's details. + +**Step 4.** If the patient is found, the system calls `showPatientViewed()` from `UI`. +- The patient's details are displayed to the user. + +The sequence diagram below illustrates how the operation for 'view-patient' would be executed in the system. + +![viewPatientSequence.png](diagrams/viewPatientSequence.png) + +--- + +### Add patient feature +The `add-patient` feature allows users to register new patients by providing their personal information. +The system ensures that the **input is valid** and that the **patient does not already exist** before adding them to the patient list. + +**Step 1.** +The user launches the application for the first time. `ClinicEase` will be initialized with the saved data (if any). +The system loads the stored list of patients and appointments. The user is now ready to register a new patient. + +**Step 2.** +The user executes the following command to add a new patient: + +`add-patient n/John Doe ic/S1234567A dob/1990-01-01 g/M p/98765432 a/123 Main St h/Diabetes, Hypertension` + +This command is read by the `ClinicEase` class and passed to the `Parser`. +The `Parser` class identifies the command as `add-patient` and parses the fields. A `Patient` object is then constructed from the parsed data. + +> **Note** +> If any required field (such as name, NRIC, or address) is missing, the parser will return `null`, and the system will display an appropriate error message. +> The patient will not be added in this case. + +**Step 3.** +The `ManagementSystem.addPatient()` method is called with the parsed input. +The method first checks if a patient with the same NRIC already exists in the system. +If the NRIC is unique and all details are present, a new `Patient` object is added to the patient list. + +> **Note** +> If the NRIC already exists, a message is shown to inform the user that the patient has already been registered. + +**Step 4.** +After successful registration, `Storage.savePatients()` is called to update the saved patient list on disk. +If saving fails, `ClinicEase` catches an `UnloadedStorageException` and alerts the user. + +The following sequence diagram shows how an `add-patient` operation flows through the system: +![add-patient](./diagrams/addPatientSequence.png) + +--- + +### Delete patient feature +The `delete-patient` feature allows users to **remove a patient** from the system using their **NRIC**. +When a patient is deleted, all their associated **appointments** are also removed to maintain data consistency. + +### Step 1. +The user launches the application. `ClinicEase` loads any saved data, including the patient list and appointments. +The user is now ready to delete a patient from the system. + +### Step 2. +The user executes the following command: + +`delete-patient S1234567A` + +This command is passed to the `Parser` class. +The `Parser` identifies the command as `delete-patient`, extracts the NRIC `S1234567A`, and passes it to a new `DeletePatientCommand`. + +> **Note** +> If the input format is incorrect (e.g., `delete-patient` with no NRIC), the parser throws an `InvalidInputFormatException` and displays an error message to the user. + +### Step 3. +The `DeletePatientCommand.execute()` method calls `ManagementSystem.deletePatient(nric)`. + +The method first **searches for the patient** with the provided NRIC in the patient list. +If found: +- The patient is removed from the list. +- All of the patient’s appointments are also removed. +- Both `patients` and `appointments` are saved using `Storage.savePatients()` and `Storage.saveAppointments()`. + +> **Note** +> If the NRIC is not found, the method returns `null` and an appropriate message is displayed. + +### Step 4. +Once the patient is successfully deleted, a confirmation message is shown to the user. +If saving to disk fails, an `UnloadedStorageException` is thrown and handled by the system to alert the user. + +The following sequence diagram shows how a `delete-patient` operation flows through the system: +![delete-patient](./diagrams/deletePatientSequence.png) + +--- + +### Add/delete appointment feature +The `add-appointment` and `delete-appointment` features allow users to manage appointments for registered patients. +The system ensures that the **patient exists** before adding the appointment and that the **appointment exists** before deleting it. +All changes are stored persistently. + +#### Add Appointment +The add-appointment feature lets users schedule appointments for patients who are already registered in the system. + +**Example usage scenario** and how the `add appointment` mechanism behaves at each step: + +Step 1. The user launches the application for the first time. The `ClinicEase` is initialized with the stored list of +patients and appointments. The user is now ready to add a new appointment. + +Step 2. The user executes the command: `add-appointment ic/S1234567D ...` to add the appointment to the appointment list. +This command let `ClinicEase` class reads the user input and passes it to the `Parser`. +The `Parser` class determines that the command is `add-appointment` and creates an `AddAppointmentCommand` object. + +```` +public static Command parse(String userInput) throws InvalidInputFormatException, UnknownCommandException { + //... + case "add-appointment": + return new AddAppointmentCommand(parseAddAppointment(userInput)); + //... +```` + +> **Note:**
+> If the input does not match the expected format, an InvalidInputFormatException is thrown. Hence, the appointment will +> not be successfully added and stored. + +Step 3. The system calls `execute()` method in `AddAppointmentCommand`. Then, this class calls `ManagementSystem.addAppointment()` +to add the appointment to the system. `ManagementSystem` checks if the patient exists using `findPatientByNRIC()`. +- If the patient is found, the system creates an `Appointment` object and adds it to the appointment list. +- Conditions the appointment fail to be added and stored: + - If the patient's NRIC does not exist (a `PatientNotFoundException` is thrown). + - If the appointment clashes with another scheduled within 1 hour (an `AppointmentClashException` is thrown). + +Step 4. After the appointment is successfully added, `Storage.saveAppointments()` is called to update the stored appointment list. +If saving fails, `ClinicEase` catches an `UnloadedStorageException` and informs the user. + +The following sequence diagram shows how an `add-appointment` operation goes through the system: +![add-appointment](./diagrams/addAppointmentSequence.png) +Note: Ui component is omitted (only represented by "Display message" here) for simplicity reason. + +To clarify how is extractValue() called to extract each parameter: +```` +public static Appointment parseAddAppointment(String input) throws InvalidInputFormatException { + //... + String nric = extractValue(temp, "ic/"); + String date = extractValue(temp, "dt/"); + String time = extractValue(temp, "t/"); + String desc = extractValue(temp, "dsc/"); + //... +```` + +#### Delete Appointment +The `delete-appointment` feature allows users to remove an appointment that is no longer required. + +**Example usage scenario** and how the `delete appointment` mechanism behaves at each step: +Step 1. Suppose the user has already added one or more appointments and wishes to delete one. The appointment must +exist in the current appointment list. + +Step 2. The user needs to know the appointment ID of the appointment to be deleted. If unsure, he/she may execute the +`list-appointment` command to view all existing appointments. + +Step 3. The user executes the command `delete-appointment A100`. Similar to `add appointment`, The `ClinicEase` class +reads the user input and passes it to the `Parser`, which creates a `DeleteAppointmentCommand` object using the provided +`APPOINTMENT_ID`. + +```` +public static Command parse(String userInput) throws InvalidInputFormatException, UnknownCommandException { + //... + case "delete-appointment": + return new DeleteAppointmentCommand(parseDeleteAppointment(userInput)); + //... +```` + +Step 4. The system calls `execute()` method in `DeleteAppointmentCommand`. Then, it calls `ManagementSystem.deleteAppointment()` +which removes the corresponding `Appointment` object from the list. + +- The system searches for the appointment with the specified `APPOINTMENT_ID`. +- If found, it removes the appointment from the list. +- It also retrieves the corresponding patient using findPatientByNric() and updates the patient's internal appointment list. + +> **Note:** +> - If the `APPOINTMENT_ID` is invalid, an error message will be displayed and the deletion will not proceed. +> - When a **specified patient** is **deleted**, all **appointment records associated** with that patient will be **removed** too. + +Step 5. After successful deletion, the system updates the stored list using `Storage.saveAppointments()`. + +The following sequence diagram shows how an `delete-appointment` operation goes through the system **(positive case, +where the appointment exists and is successfully deleted):** +![delete-appointment](./diagrams/deleteAppointmentSequence.png) + +### Why they are implemented this way +The current design separates command parsing (`Parser`), command execution (`Command` subclasses), and core logic (`ManagementSystem`). +This structure helps improve modularity, testability, and clarity in our codebase. + +- By using distinct `Command` classes (`AddAppointmentCommand`, `DeleteAppointmentCommand)`, each operation is encapsulated with its own logic, making it easier to maintain and extend. + +- Centralizing the data logic in ManagementSystem so that it is easier to maintain and test appointment-related operations. + +- Validating `NRIC` and `APPOINTMENT_ID` before performing operations could prevent invalid data from entering the system and improves user experience by providing clear error feedback. + +This design also aligns with the **Separation of Concerns** principle, allowing changes in one component (e.g., how appointments are stored) +without affecting others (e.g., how commands are parsed or executed). + +### Design considerations: +#### Aspect: Where to store appointment data + +1. **Alternative 1 (current choice)**: Maintain a centralized appointment list in `ManagementSystem` and update each patient's internal appointment list. + - **Pros:** Enables efficient listing, searching, and clash detection. + - **Cons:** Requires synchronization between the central list and per-patient records (slight redundancy). + + +2. **Alternative 2:** Allowing appointment creation without verifying patient existence
+ - **Pros:** Simplifies implementation as patient verification is not needed. + - **Cons:** Compromises data integrity, as appointments could become orphaned (unlinked to valid patients). + + +3. **Alternative 3:** Store appointments exclusively in `Patient` objects
+ - **Pros:** Keeps appointment data stored within each patient and reduces need for cross-references. + - **Cons:** Aggregating all appointments becomes inefficient (require + iterating through every patient); harder to search across patients and detect conflicts. + +--- + +### Storing Medical History Feature + +The `store-history` feature allows users to **add new medical history entries** for a specific patient in the system. +The system verifies that the **patient exists** before adding the entries, then the updated data is saved persistently. + +Below is a usage scenario illustrating how the `store-history` mechanism behaves step by step. + +**Step 1.** The user executes a command such as: + +store-history n/John Doe ic/S1234567A h/Diabetes,High Cholesterol +- **ClinicEase** reads this command and passes the input string to **Parser**. +- **Parser** identifies the command as `store-history` (based on the command word) and extracts the relevant parameters (`name`, `nric`, `h/` tokens). +- If the input is invalid (missing or malformed parameters), an `InvalidInputFormatException` is thrown, aborting the process. + +**Step 2.** **Parser** creates a `StoreMedHistoryCommand` object with the extracted details: + +- `StoreMedHistoryCommand` holds the `name` ("John Doe"), `nric` ("S1234567A"), and `medHistory` string ("Diabetes,High Cholesterol"). + +**Step 3.** `ClinicEase` invokes `StoreMedHistoryCommand#execute(...)`, which calls: + +1. `ManagementSystem.storeMedicalHistory(name, nric, medHistory)` +2. `ManagementSystem` checks if the patient exists using `findPatientByNric(nric)`. + - If **not found**, it prints an error, and **no** changes are made to storage. + - If **found**, it splits `"Diabetes,High Cholesterol"` into an array of entries: + - `"Diabetes"` + - `"High Cholesterol"` + - Then, it appends these entries to the patient's existing `medicalHistory` list (skipping duplicates). + +**Step 4.** `ManagementSystem` calls `Storage.savePatients(...)` to persist any changes to the patient data on disk: + +- If saving fails, `UnloadedStorageException` is thrown, and `ClinicEase` displays an error to the user. + +Below is a detailed **PlantUML** sequence diagram showing how a `store-history` operation moves through the system and includes the check for a valid patient: +![add-appointment](./diagrams/storeMedicalHistorySequence.png) + +--- + +### Prescription Management Feature + +The `add-prescription`, `view-prescription`, and `view-all-prescriptions` features allow doctors and medical staff to create and track medication prescriptions for patients within the clinic system. The system ensures that the **patient exists** before adding a prescription and handles the generation of unique prescription IDs and HTML documents for printing. + +#### Adding a Prescription + +The add-prescription feature allows users to create detailed prescriptions with symptoms, medicines, and optional notes for registered patients. + +**Example usage scenario** and how the `add-prescription` mechanism behaves at each step: + +Step 1. The user launches the application. The `ClinicEase` is initialized with stored patient, appointment, and prescription data. The user is now ready to add a new prescription. + +Step 2. The user executes the command: +``` +add-prescription ic/S1234567A s/Fever,Cough m/Paracetamol,Cough syrup nt/Take after meals +``` + +This command lets `ClinicEase` read the user input and pass it to the `Parser`. The `Parser` determines that the command is `add-prescription` and creates an `AddPrescriptionCommand` object with a new `Prescription` object. + +```java +public static Command parse(String userInput) throws InvalidInputFormatException, UnknownCommandException { + //... + case "add-prescription": + return new AddPrescriptionCommand(parseAddPrescription(userInput)); + //... +} +``` + +> [!NOTE]: +> If the input does not match the expected format, an `InvalidInputFormatException` is thrown. Hence, the prescription will not be successfully added and stored. + +Step 3. The system calls `execute()` method in `AddPrescriptionCommand`. This class calls `ManagementSystem.addPrescription(prescription)` to add the prescription to the system. + +The `ManagementSystem` verifies the patient exists using `findPatientByNric()`: +- If the patient is found, the system generates a proper prescription ID and creates a new `Prescription` object. +- If the patient's NRIC does not exist, an `IllegalArgumentException` is thrown. + +Step 4. After the prescription is successfully added, `Storage.savePrescriptions()` is called to update the stored prescriptions list. If saving fails, an `UnloadedStorageException` is thrown and ClinicEase informs the user. + +The following sequence diagram shows how an `add-prescription` operation flows through the system: +![add-prescription](./diagrams/prescriptionManagementSequence.png) + +#### Viewing Prescriptions + +The system provides two commands for viewing prescriptions: + +1. **`view-all-prescriptions PATIENT_ID`** - Shows all prescriptions for a specific patient. +2. **`view-prescription PRESCRIPTION_ID`** - Shows details of a specific prescription and generates an HTML version. + +**Example usage scenario** for `view-prescription`: + +Step 1. The user needs to view a specific prescription and generate its printable HTML version. If the user knows the prescription ID, they can directly proceed to step 2. Otherwise, they may need to first use `view-all-prescriptions` to find the required ID. + +Step 2. The user executes the command `view-prescription S1234567A-1`. The `Parser` extracts the prescription ID and creates a `ViewPrescriptionCommand` object. + +Step 3. The system calls `execute()` in `ViewPrescriptionCommand`. The system fetches: + - The prescription using `ManagementSystem.getPrescriptionById()` + - The patient information using `ManagementSystem.viewPatient()` + +Step 4. If the prescription is found, the system: + - Displays the prescription details to the user + - Generates an HTML file with `Storage.savePrescriptionHtml()` + - Shows the file location to the user + +> **Note:** +> If the prescription ID is invalid, an error message will be displayed and no HTML will be generated. + +#### HTML Prescription Generation + +An important feature of the prescription management system is the generation of printable HTML documents. This functionality is embedded within the `Prescription` class: + +```java +public String generateHtml(Patient patient) { + StringBuilder html = new StringBuilder(); + // Generate structured HTML with prescription details + // Include patient information when available + // Add print button and styling + return html.toString(); +} +``` + +The generated HTML provides: +- A professional format for printing +- All prescription details (ID, timestamp, symptoms, medicines) +- Patient information when available +- A print button for easy printing from any browser + +#### Why It's Implemented This Way + +The current design separates command parsing (`Parser`), command execution (command classes like `AddPrescriptionCommand`), and core logic (`ManagementSystem` and `Prescription`). This structure allows: + +- Better modularity by separating different aspects of functionality +- Improved testability with clear interfaces between components +- Consistent approach with other features in the system +- Easy extension for future prescription-related features + +The HTML generation approach was chosen as it provides a printable output without requiring additional libraries, while maintaining visual quality needed for medical documentation. + +#### Class Structure + +The implementation follows these key classes: + +1. **Prescription** - Core class representing a prescription with all its attributes and conversion methods. +2. **Command Classes:** + - **AddPrescriptionCommand** - Creates new prescriptions + - **ViewPrescriptionCommand** - Displays a prescription and generates HTML + - **ViewAllPrescriptionsCommand** - Lists all prescriptions for a patient +3. **ManagementSystem** - Maintains the list of prescriptions and provides methods to add and retrieve them +4. **Storage** - Handles saving and loading prescriptions from disk + +The class diagram below shows the relationships between these classes: +![prescription-classes](./diagrams/prescriptionClassDiagram.png) + +#### Design Considerations + +##### Aspect: Prescription Identification + +* **Alternative 1 (current choice):** Patient ID plus sequence number (e.g., "S1234567A-1"). + * Pros: + * Clear association between patients and their prescriptions + * Easy for staff to understand and reference verbally + * Intuitive sequential numbering for prescriptions + * Cons: + * Requires tracking the last used number for each patient + +* **Alternative 2:** Generate UUIDs for prescriptions. + * Pros: + * Guaranteed global uniqueness + * No need for sequence management + * Cons: + * Not human-friendly for verbal reference + * No visual connection to the patient ID + +##### Aspect: Prescription Output Format + +* **Alternative 1 (current choice):** HTML documents for prescriptions. + * Pros: + * Universal browser support for viewing and printing + * No external dependencies required + * Responsive design across devices + * Cons: + * Not a standardized medical document format + +* **Alternative 2:** PDF documents. + * Pros: + * Industry standard format for medical documents + * Better control over print layouts + * Cons: + * Requires external PDF library dependencies + * Increases complexity and dependencies + +##### Aspect: Storage Format + +* **Alternative 1 (current choice):** Simple pipe-delimited text storage. + * Pros: + * Consistency with other system data formats + * Easy to parse and maintain + * Human-readable in storage + * Cons: + * Limited handling of special characters + +* **Alternative 2:** JSON format. + * Pros: + * Better handling of complex data structures + * Standard data interchange format + * Cons: + * Requires additional parsing libraries + * Inconsistent with the system's other storage formats + +--- + +
+ +## Appendix: Requirements ## Product scope ### Target user profile -{Describe the target user profile} +The target users are clinic staff, such as receptionists, assistants, or solo practitioners, who need a lightweight, no-frills system to manage patient records and appointment schedules. These users are assumed to be comfortable with basic command-line interfaces but may not have advanced technical expertise. ### Value proposition -{Describe the value proposition: what problem does it solve?} +This CLI-based Clinic Management System offers a simple yet effective solution for managing patient data and appointments without the need for complex software installations or internet access. It helps clinics save time, stay organized, and reduce manual errors by streamlining common administrative tasks like adding patients, scheduling appointments, and retrieving records, all from the command line. + +--- ## User Stories -|Version| As a ... | I want to ... | So that I can ...| -|--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| + +| Version | As a ... | I want to ... | So that I can ... | +|---------|----------|-----------------------------------------------------|-----------------------------------------------------------------| +| v1.0 | doctor | add my patients' personal details | I can add them into the system | +| v1.0 | doctor | delete my patients' personal details | I can remove them from the system | +| v1.0 | doctor | view my certain patient's personal details | I can view them in the system | +| v1.0 | doctor | add appointments into my schedule | I can add appointments plan from a patient needed to be tracked | +| v1.0 | doctor | delete appointments from my schedule | I can get rid of appointments no longer needed to track | +| v1.0 | doctor | list my upcoming appointments | I can manage my time effectively without manual scheduling | +| v1.0 | doctor | store patients' medical history | I can understand the patient's situation better | +| v1.0 | doctor | check all medical histories for one certain patient | I can know what happened before the patient come | +| v2.0 | doctor | add a new prescription for a patient | record the prescribed medications and instructions | +| v2.0 | doctor | view all prescriptions for a patient | track the patient's medication history | +| v2.0 | doctor | add symptoms to a prescription | document the patient's condition | +| v2.0 | doctor | add special notes to prescriptions | provide additional instructions to patients | +| v2.0 | doctor | generate a printable prescription | provide a professional document to the patient | +| v2.0 | doctor | edit my patients' personal details | update them if there is any updates | +| v2.0 | doctor | sort appointments by date | check which appointements are coming first | +| v2.0 | doctor | mark/unmark appointments | track my appointments more easily | + +--- + +## Use Cases + +### Use Case: Add a Patient + +#### MSS +User requests to add a patient, adding the personal details. +ClinicEase adds the patient to the system. +ClinicEase confirms the patient has been added successfully. + +Use case ends. + +#### Extensions +1a. Required details are missing or incorrectly formatted. +1a1. ClinicEase displays an error message. +Use case resumes at step 1. + +1b. A patient with the same NRIC already exists. +1b1. ClinicEase displays an error message. +Use case ends. + +--- + +### Use Case: Delete a Patient + +#### MSS +User requests to list patients. +ClinicEase displays a list of patients. +User requests to delete a specific patient from the list. +ClinicEase deletes the patient. + +Use case ends. + +#### Extensions +2a. The patient list is empty. +Use case ends. + +3a. The given NRIC is invalid. +3a1. ClinicEase displays an error message. +Use case resumes at step 2. + +--- + +### Use Case: Add an Appointment + +#### MSS +User requests to add appointment, specifying the patient NRIC, date, time, and description. +ClinicEase verifies that the patient exists. +ClinicEase adds the appointment to the system. +ClinicEase confirms that the appointment has been added successfully. + +Use case ends. + +#### Extensions +2a. The patient does not exist in the system. +2a1. ClinicEase displays an error message. +Use case ends. + +1a. Required details are missing or incorrectly formatted. +1a1. ClinicEase displays an error message. +Use case resumes at step 1. + +--- + +### Use Case: Delete an Appointment + +#### MSS +User requests to list appointments. +ClinicEase displays a list of appointments. +User requests to delete a specific appointment. +ClinicEase deletes the appointment. + +Use case ends. + +#### Extensions +2a. The appointment list is empty. +Use case ends. + +3a. The given appointment ID is invalid. +3a1. ClinicEase displays an error message. +Use case resumes at step 2. + +--- + +### Use Case: View a Patient's Medical History + +#### MSS +User requests to view patient's medical history using the patient's NRIC. +ClinicEase retrieves and displays the medical history of the patient. + +Use case ends. + +#### Extensions +1a. The given NRIC is invalid or does not exist. +1a1. ClinicEase displays an error message. +Use case ends. + +2a. The patient has no recorded medical history. +2a1. ClinicEase informs the user that no history is available. +Use case ends. + +--- + +### Use Case: Edit a Patient's Details + +#### MSS +User requests to edit a patient's details using the patient's NRIC and the new details. +ClinicEase verifies that the patient exists. +ClinicEase updates patient's details. +ClinicEase confirms the update was successful. + +Use case ends. + +#### Extensions +2a. The patient does not exist in the system. +2a1. ClinicEase displays an error message. +Use case ends. + +1a. The provided new details are invalid or missing. +1a1. ClinicEase displays an error message. +Use case resumes at step 1. + +--- + +### Use Case: Sort Appointments + +#### MSS +User requests to sort appointments by date or by appointment ID. +ClinicEase sorts the appointments accordingly. +ClinicEase displays the sorted list. + +Use case ends. + +#### Extensions +1a. The appointment list is empty. +1a1. ClinicEase displays a message indicating there are no appointments to sort. +Use case ends. + +1b. The sorting parameter is invalid. +1b1. ClinicEase displays an error message. +Use case resumes at step 1. + +--- + +### Use case: Add a new prescription + +**MSS** + +1. Doctor requests to add a new prescription +2. System prompts for prescription details (patient ID, symptoms, medicines, optional notes) +3. Doctor enters the required information +4. System validates the patient ID exists +5. System generates a unique prescription ID +6. System saves the prescription +7. System displays success message with the prescription details + + Use case ends. + +**Extensions** + +* 4a. Patient ID does not exist + * 4a1. System shows an error message + * 4a2. Use case resumes at step 2 + +* 3a. Required fields are missing + * 3a1. System shows error message with correct format + * 3a2. Use case resumes at step 2 + +--- + +### Use case: View all prescriptions for a patient + +**MSS** + +1. Doctor requests to view all prescriptions for a patient +2. Doctor enters patient ID +3. System validates patient exists +4. System retrieves and displays all prescriptions for the patient + + Use case ends. + +**Extensions** + +* 3a. Patient ID does not exist + * 3a1. System shows an error message + * 3a2. Use case ends + +* 4a. No prescriptions found + * 4a1. System shows "No prescriptions found" message + * 4a2. Use case ends + +--- + +### Use case: View and generate HTML prescription + +**MSS** + +1. Doctor requests to view a specific prescription +2. Doctor enters prescription ID +3. System validates prescription exists +4. System displays prescription details +5. System generates HTML version +6. System shows location of generated file + + Use case ends. + +**Extensions** + +* 3a. Prescription ID does not exist + * 3a1. System shows an error message + * 3a2. Use case ends + +* 5a. HTML generation fails + * 5a1. System shows error message + * 5a2. Use case ends + +--- ## Non-Functional Requirements +1. Should work on any mainstream OS as long as it has Java `17` or above installed. +2. The system should efficiently manage up to 1000 patients or appointments without any noticeable performance lag during typical usage. +3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks within 30 seconds using CLI commands. +4. All error messages should clearly indicate how to correct the invalid input. +5. Data files should be compatible across different OS platforms. +6. All functionality should be accessible via keyboard-only commands. -{Give non-functional requirements} +--- ## Glossary +* *Mainstream OS* - Windows, Linux, Unix, macOS +* *Performance Lag* – A noticeable delay or slowdown in the system's response to user actions. +* *Error Messages* – System-generated messages that inform users of incorrect input and provide guidance on how to fix it. +* *OS Platform Compatibility* – The ability for data files and system functionality to work consistently across different operating systems. + +--- + +
+ +## Appendix: Instructions for Manual Testing + +Below is a suggested guide for **manual testing** of the ClinicEase application in a Command Line Interface (CLI) environment. + + +## 1. Getting Started + +1. **Compilation** + - Navigate to the project's root folder (where the `ClinicEase.java` and other `.java` files reside). + - Compile the source files. For example: + ``` + javac *.java + ``` + - Alternatively, use your favorite IDE's build tool. + +2. **Launching the Application** + - Run the compiled main class: + ``` + java ClinicEase + ``` + You should see a welcome message that looks like this: + ``` + -------------------------------------------------------------------------------- + Welcome to ClinicEase! + Type a command, or 'bye' to exit. + -------------------------------------------------------------------------------- + > + ``` + +3. **Exiting the Application** + - To exit, type: + ``` + bye + ``` + - Expected output: + ``` + -------------------------------------------------------------------------------- + Goodbye! + -------------------------------------------------------------------------------- + ``` + - The program will then terminate. + +--- + +## 2. Testing Patient Management Features + +### 2.1 Add a New Patient + +**Command Format**: `add-patient n/NAME ic/NRIC dob/BIRTHDATE g/GENDER p/PHONE a/ADDRESS [h/MEDICAL_HISTORY]` + +- `[h/MEDICAL_HISTORY]` is optional and can be multiple entries separated by commas. + +**Steps to Test** +1. Input: + ``` + add-patient n/Alice Tan ic/S1234567A dob/1990-01-01 g/F p/91234567 a/123 Bedok Road h/High blood pressure + ``` +2. Expected output: + ``` + -------------------------------------------------------------------------------- + Patient added successfully: Alice Tan + -------------------------------------------------------------------------------- + ``` +3. The system should store the new patient data (written to `patient_data.txt`). + +**Additional Test Cases** +- **Missing required fields** (e.g., no `a/ADDRESS`) should produce an `InvalidInputFormatException` message. +- **Duplicate NRIC** should produce a `DuplicatePatientIDException`. + + +### 2.2 List All Patients + +**Command Format**: `list-patient` + +**Steps to Test** +1. Input: + ``` + list-patient + ``` +2. If you have existing patients, the system displays each patient in a list format: + ``` + ------------------------------------------Patient Details------------------------------------------ + Patient NRIC: S1234567A Name: Alice Tan ... + -------------------------------------------------------------------------------- + ``` +3. If no patients exist, the system prints: + ``` + -------------------------------------------------------------------------------- + No patients have been added. + -------------------------------------------------------------------------------- + ``` + + +### 2.3 View a Patient by NRIC + +**Command Format**: `view-patient NRIC` + +**Steps to Test** +1. Input: + ``` + view-patient S1234567A + ``` +2. If the patient is found, detailed information is displayed. Otherwise, the system notifies you that no matching patient was found. + + +### 2.4 Delete a Patient + +**Command Format**: `delete-patient NRIC` + + +**Steps to Test** +1. Input: + ``` + delete-patient S1234567A + ``` +2. If the patient exists, the system confirms deletion: + ``` + -------------------------------------------------------------------------------- + No patients have been added. + -------------------------------------------------------------------------------- + ``` +3. If the patient doesn't exist, it notifies you accordingly. + + +### 2.5 Edit Patient Information + +**Command Format** `edit-patient ic/NRIC [n/NAME] [dob/BIRTHDATE] [g/GENDER] [a/ADDRESS] [p/PHONE]` + +- `ic/NRIC` is required to locate the patient. +- The remaining fields are optional; include only those you want to edit. + +**Steps to Test** +1. Input: + ``` + edit-patient ic/S1234567A n/Alice Tan g/F a/321 Jurong Avenue + ``` +2. Expected output upon success: + ``` + -------------------------------------------------------------------------------- + Patient with NRIC S1234567A updated successfully. + Edit-patient command executed. + -------------------------------------------------------------------------------- + ``` +3. Use `view-patient S1234567A` to confirm the updated details. + +--- + +## 3. Testing Medical History Features + +### 3.1 Store Medical History + +**Command Format**: `store-history n/NAME ic/NRIC h/HISTORY` + +- `h/HISTORY` can contain multiple entries separated by commas. + +**Steps to Test** +1. Input: + ``` + store-history n/Bob Lee ic/S7654321B h/Diabetes,High cholesterol + + ``` +2. If the patient doesn't exist, the system creates a new one and prints a confirmation message. If the patient exists, it simply adds new history entries. + + +### 3.2 View Medical History + +**Command Format**: +1. By NRIC: `view-history ic/NRIC` +2. By Name: `view-history NAME` + + +**Steps to Test** +1. Input: + ``` + view-history ic/S7654321B + ``` +2. Displays the patient's history if found. Otherwise, notifies you that it cannot find the patient. + +--- + +### 3.3 Edit Medical History + +**Command Format**: `edit-history ic/NRIC old/OLD_TEXT new/NEW_TEXT` + +**Steps to Test** +1. Input: + ``` + edit-history ic/S7654321B old/Diabetes new/Type 2 Diabetes + ``` +2. If `old/Diabetes` matches an existing record, the system replaces it with `Type 2 Diabetes` and prints a confirmation message. + +--- + +## 4. Testing Appointment Features + +### 4.1 Add an Appointment + +**Command Format**: `add-appointment ic/NRIC dt/DATE t/TIME dsc/DESCRIPTION` + +- Date: `yyyy-MM-dd` +- Time: `HHmm` (24-hour format) + +**Steps to Test** +1. Input: + ``` + add-appointment ic/S1234567A dt/2025-12-01 t/0930 dsc/Dental Checkup + ``` +2. If the patient is found, the system adds the appointment and shows a success message. If the patient doesn't exist, it prints an error. + + +### 4.2 List Appointments + +**Command Format**: `list-appointment` + +**Steps to Test** +1. Input: + ``` + list-appointment + ``` + +2. Shows all appointments if any exist. Otherwise, prints a "No appointments found" message. + + +### 4.3 Sort Appointments + +**Command Format**: +1. by date:`sort-appointment byDate` +2. by appointment id: `sort-appointment byId` + +**Steps to Test** +1. Input: + ``` + sort-appointment byDate + ``` +- Appointments should be sorted chronologically. + +2. Input: + ``` + sort-appointment byId + ``` +- Appointments should be sorted by their `Axxx` IDs. + + +### 4.4 Mark and Unmark an Appointment + +**Command Format**: `mark-appointment APPOINTMENT_ID unmark-appointment APPOINTMENT_ID` + +**Steps to Test** +1. Input: + ``` + mark-appointment A100 + ``` +- The system marks the appointment as done (`[X]`). +2. Input: + ``` + unmark-appointment A100 + ``` +- The system reverts the appointment to undone (`[ ]`). + + +### 4.5 Find an Appointment by NRIC + +**Command Format**: `find-appointment NRIC` + +**Steps to Test** +1. Input: + ``` + find-appointment S1234567A + ``` +2. If any matching appointment is found, it prints the details. Otherwise, it prints "No appointment found." + +--- + +## 5. Testing Prescription Management Features + +### 5.1 Adding a New Prescription + +**Command Format**: `add-prescription ic/PATIENT_ID s/SYMPTOMS m/MEDICINES [nt/NOTES]` + +- `[nt/NOTES]` is optional and can include special instructions. +- Multiple symptoms and medicines can be separated by commas. + +**Steps to Test** +1. Prerequisites: Patient with ID "S9876543B" exists in the system. + +2. Input: + ``` + add-prescription ic/S9876543B s/Fever, Cough m/Paracetamol, Cough syrup nt/Take after meals + ``` + * Expected output: Prescription is added. Details of the new prescription shown. + +**Additional Test Cases** +- **Missing medicines field**: + ``` + add-prescription ic/S9876543B s/Fever m/ + ``` + * Expected output: Error shown. Missing medicines field. + +- **Non-existent patient**: + ``` + add-prescription ic/X1234567Y s/Fever m/Paracetamol + ``` + * Expected output: Error shown. Patient ID does not exist. + + +### 5.2 Viewing Prescriptions + +**Command Format**: +1. View all for a patient: `view-all-prescriptions PATIENT_ID` +2. View specific prescription: `view-prescription PRESCRIPTION_ID` + +**Steps to Test** +1. Prerequisites: At least one prescription exists for patient "S9876543B". + +2. Input: + ``` + view-all-prescriptions S9876543B + ``` + * Expected output: List of all prescriptions for the patient shown. + +3. Input: + ``` + view-prescription S9876543B-1 + ``` + * Expected output: Details of the specific prescription shown. HTML file generated. + +**Additional Test Cases** +- **Invalid prescription ID**: + ``` + view-prescription INVALID-ID + ``` + * Expected output: Error shown. Invalid prescription ID. + + +### 5.3 Generating HTML Prescriptions + +**Command Format**: `view-prescription PRESCRIPTION_ID` + +**Steps to Test** +1. Prerequisites: Valid prescription exists with ID "S9876543B-1". + +2. Input: + ``` + view-prescription S9876543B-1 + ``` + * Expected output: HTML file generated in data/prescriptions folder. + * Verification: Open the generated HTML file in a browser. Check that all prescription details are correctly displayed. + +--- + +## 6. Error Handling Scenarios + +- **Unknown Commands** +- If you type something invalid like `randomCommand`, the system should respond: + ``` + Unknown command. Please try again. + ``` +- **Missing or Invalid Parameters** +- For instance, `add-appointment` missing the `dt/DATE` should trigger an error message (`InvalidInputFormatException`). +- **Storage Failures** +- If there's an I/O error with reading or writing to `patient_data.txt`, you might see `UnloadedStorageException`. + +--- + +## 7. Comprehensive Test Workflow -* *glossary item* - Definition +1. **Add multiple patients** and confirm they appear correctly with `list-patient`. +2. **Add detailed medical histories** with `store-history`; verify them using `view-history`. +3. **Add appointments** to different patients and use `list-appointment`, `sort-appointment`, `mark-appointment`, etc. to test appointment functionality. +4. **Add prescriptions** to patients and test the prescription view and HTML generation features. +5. **Delete a patient** and confirm the removal. +6. **Exit** the program with `bye`. -## Instructions for manual testing +--- -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..bbce7c9ff9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# Duke +# ClinicEase -{Give product intro here} +ClinicEase is an application designed to assist doctors in managing patient records and appointments efficiently. It allows the user to add, edit, view, and delete patient information and medical history, as well as schedule and track appointments with ease. With its intuitive command-based interface, ClinicEase ensures smooth and reliable clinic operations. Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index d6cf4c3b3a..93ac878249 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -2,41 +2,597 @@ ## Introduction -{Give a product intro} +ClinicEase is an application designed to assist doctors in managing patient records and appointments efficiently. It allows the user to add, edit, view, and delete patient information and medical history, as well as schedule and track appointments with ease. With its intuitive command-based interface, ClinicEase ensures smooth and reliable clinic operations. -## Quick Start +--- +## Table of Contents +- [Quick Start](#quick-start) +- [Features](#features-) + - [Viewing help: `help`](#viewing-help-help) + - Managing Patients + - [Adding a new patient: `add-patient`](#adding-a-new-patient--add-patient) + - [Deleting a patient: `delete-patient`](#deleting-a-patient--delete-patient) + - [Viewing patient details: `view-patient`](#viewing-patient-details-view-patient) + - [Listing all patients: `list-patient`](#listing-all-patients-list-patient) + - [Editing a specified patient: `edit-patient`](#editing-a-specified-patient-edit-patient) + - Managing Appointments + - [Adding an appointment: `add-appointment`](#adding-an-appointment-add-appointment) + - [Deleting an appointment: `delete-appointment`](#deleting-an-appointment-delete-appointment) + - [Sorting appointments: `sort-appointment`](#sorting-appointments-sort-appointment) + - [Marking an appointment as done: `mark-appointment`](#marking-an-appointment-as-done-mark-appointment) + - [Unmarking a completed appointment: `unmark-appointment`](#unmarking-a-completed-appointment-unmark-appointment) + - [Finding a patient's appointments: `find-appointment`](#finding-a-patients-appointments-find-appointment) + - Managing Prescriptions + - [Adding a prescription: `add-prescription`](#adding-a-prescription-add-prescription) + - [Viewing all prescriptions: `view-all-prescriptions`](#viewing-all-prescriptions-view-all-prescriptions) + - [Viewing specific prescription: `view-prescription`](#viewing-specific-prescription-view-prescription) + - Updating Medical History + - [Adding medical history: `store-history`](#adding-medical-history-store-history) + - [Viewing medical history: `view-history`](#viewing-medical-history-view-history) + - [Editing medical history: `edit-history`](#editing-medical-history-edit-history) + - [Exiting the program: `bye`](#exiting-the-program-bye) +- [FAQ](#faq) +- [Known Issues](#known-issues) +- [Command Summary](#command-summary) -{Give steps to get started quickly} +--- +## Quick Start -1. Ensure that you have Java 17 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +1. Ensure that you have Java 17 or above installed.
+ **Mac users:** Ensure you have the precise JDK version prescribed + [here](https://se-education.org/guides/tutorials/javaInstallationMac.html). +2. Download the latest version of `ClinicEase` [here](https://github.com/AY2425S2-CS2113-T11b-4/tp/releases). +3. Copy the file to the folder you want to use as the home folder for ClinicEase. +4. Open a command terminal, `cd` into the folder you put the jar file in, and use the + `java -jar ClinicEase.jar` command to run the application. +5. Type the command in the command box and press Enter to execute it.
+ Some example commands you can try: + - `add-patient n/John ic/S1234567D dob/2003-04-06 g/M p/91218188 a/123 Main St`:
+ Adds a patient named `John` to the patient list. + - `delete-patient S1234567D`: Deletes the patient with NRIC `S1234567D` in the patient list. + - `bye`: Exits the app. +6. Refer to the Features below for details of each command. +
+--- ## Features +Here are some feature that the product provides: + +> [!NOTE] +> - Words in `UPPER_CASE` represent parameters that must be provided by the user.
+ e.g. in `view-patient NRIC`, `NRIC` is a parameter which can be used as `view-patient S1234567D`. +> - Items in square brackets are optional.
+ e.g. `ic/NRIC [n/NAME] [dob/BIRTHDATE]` can be used as `ic/S1234567D n/John` or as `ic/S1234567D dob/2002-06-07`. +> - Every parameter must be supplied by the user.
+ e.g. if the command specifies `add-appointment ic/NRIC dt/DATE t/TIME dsc/DESCRIPTION`, the user + must fill in all parameters for the input to be valid. +> - Parameters could be entered in any order.
+ e.g. if the command recommends `ic/NRIC dt/DATE t/TIME dsc/DESCRIPTION`, the user + can also not follow this exact sequence for the input to be valid. +> - Extraneous parameters for commands that do not take in parameters (such as `list` and `bye`) will be ignored.
+ e.g. if the command specifies `list-patient 12345`, it will be interpreted as `list-patient`. +> - Command words are **case-insensitive**.
+ e.g. `liST-paTIEnt` will be interpreted as `list-patient`. + + + +### Viewing help: `help` +Shows all available commands of the application. + +Format & Example of Usage: + +`help` + +Expected output: +
----------------------------------------------------------------------------------------------------
+These are the available commands:
+Patient: 
+- add-patient n/NAME ic/NRIC dob/BIRTHDATE g/GENDER p/PHONE a/ADDRESS
+- delete-patient NRIC
+- edit-patient ic/NRIC [n/NAME] [dob/BIRTHDATE] [g/GENDER] [a/ADDRESS] [p/PHONE]
+- list-patient
+- view-patient NRIC
+- store-history n/NAME ic/NRIC h/MEDICAL_HISTORY
+- view-history NRIC or view-history NAME
+- edit-history ic/NRIC old/OLD_TEXT new/NEW_TEXT
+Appointment: 
+- add-appointment ic/NRIC dt/DATE t/TIME dsc/DESCRIPTION
+- delete-appointment APPOINTMENT_ID
+- list-appointment
+- mark-appointment APPOINTMENT_ID
+- unmark-appointment APPOINTMENT_ID
+- sort-appointment byDate or sort-appointment byId
+- find-appointment PATIENT_NRIC
+Prescription: 
+- add-prescription ic/PATIENT_ID s/SYMPTOMS m/MEDICINES [nt/NOTES]
+- view-all-prescriptions PATIENT_ID
+- view-prescription PRESCRIPTION_ID
+----------------------------------------------------------------------------------------------------
-{Give detailed description of each feature} +
-### Adding a todo: `todo` -Adds a new item to the list of todo items. +### Adding a new patient : `add-patient` +Adds a new patient to the system with their credentials. -Format: `todo n/TODO_NAME d/DEADLINE` +Format: `add-patient n/NAME ic/NRIC dob/BIRTHDATE g/GENDER p/PHONE a/ADDRESS h/MEDICAL_HISTORY` -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +* The `NRIC` must be unique to the existing ones in the system. +* All inputs doesn't handle every ASCII characters, only alphabets and numbers. +* `h/MEDICAL_HISTORY` is optional, so users can use add-patient without it. +* `p/PHONE` & `a/ADDRESS` can be duplicate, since some patient may be represented by the same phone number and/or address. Example of usage: -`todo n/Write the rest of the User Guide d/next week` +`add-patient n/John Doe ic/S1234567D dob/1999-12-12 g/M p/98765432 a/123 Main Street h/Diabetes, Hypertension` + +Expected output: + +
---------------------------------------------------------------------------------------------------- 
+Patient added successfully: John Doe 
+---------------------------------------------------------------------------------------------------- 
+
+ +### Deleting a patient : `delete-patient` +Deletes an existing patient in the system. + +Format: `delete-patient NRIC` + +* The `NRIC` must be of a patient existing in the system. + +Example of usage: + +`delete-patient S1234567D` + +Expected output: + +
---------------------------------------------------------------------------------------------------- 
+Patient deleted successfully: John Doe
+---------------------------------------------------------------------------------------------------- 
+
+ +### Viewing patient details: `view-patient` +Displays the details of a specific patient. + +Format: `view-patient NRIC` + +* The `NRIC` must be of a patient existing in the system. + +Example of usage: + +`view-patient S1234567D` + +Expected Output: + +
------------------------------------------Patient Details------------------------------------------
+Patient NRIC: S1234567D
+Name: John Doe
+Date of Birth: 1999-12-12
+Gender: M
+Address: 123 Main Street
+Contact: 98765432
+Medical History: Diabetes, Hypertension
+Appointments: None
+----------------------------------------------------------------------------------------------------
+
+ +### Listing all patients: `list-patient` +Displays a list of all registered patients in the system with details provided. + +Format: `list-patient` + +Example of usage: + +`list-patient` + +Expected Output: + +
------------------------------------------Patient Details------------------------------------------
+1. Patient NRIC: S1234567D
+   Name: John Doe
+   Date of Birth: 1999-12-12
+   Gender: M
+   Address: 123 Main Street
+   Contact: 98765432
+   Medical History:
+   - Diabetes
+   - Hypertension
+   Appointments: None
+----------------------------------------------------------------------------------------------------
+2. Patient NRIC: S8765432F
+   Name: Jane Donna
+   Date of Birth: 2000-05-19
+   Gender: F
+   Address: 546 Main Street
+   Contact: 91209310
+   Medical History:
+   - Cough
+   Appointments: None
+----------------------------------------------------------------------------------------------------
+
+ +### Editing a specified patient: `edit-patient` +Edits a specific patient credentials by NRIC. + +Format: `edit-patient ic/NRIC ATTRIBUTE` + +* The `NRIC` must be of a patient existing in the system. +* The `ATTRIBUTE` can possibly be between `n/NAME`, `dob/BIRTHDATE`, `g/GENDER`, `a/ADDRESS`, or `p/PHONE` + +Example of usage: + +`edit-patient ic/S1234567D p/91238989` or `edit-patient ic/S1234567D dob/1945-08-17` + +Expected output: + +
Patient with NRIC S1234567D updated successfully. 
+----------------------------------------------------------------------------------------------------.
+Edit-patient command executed.
+---------------------------------------------------------------------------------------------------- 
+
+ +
+ +### Adding an appointment: `add-appointment` +Adds a new appointment to the list of appointment. + +Format: `add-appointment ic/NRIC dt/DATE t/TIME dsc/DESCRIPTION` + +* The patient with the specified `NRIC` **must** exist in the system. +* `DATE` format: `yyyy-MM-dd`, where `yyyy` is year, `MM` is month, + `dd` is day (e.g., `2025-03-31`). +* `TIME` format: `HHmm` in 24-hour format (e.g., `1430` for 2:30 PM). +* `DATE` and `TIME` **must not** be **before current date/time**. +* The new appointment will only be added if it does not clash with the others. + +Example of usage: + +`add-appointment ic/S1234567D dt/2025-03-31 t/1200 dsc/Annual checkup` + +Expected output: + +
----------------------------------------------------------------------------------------------------
+Appointment added for NRIC: S1234567D on 2025-03-31 at 12:00 PM.
+Now you have 1 appointment(s) in the list.
+----------------------------------------------------------------------------------------------------
+
+ +### Deleting an appointment: `delete-appointment` +Deletes a specified appointment from the appointment list. + +Format: `delete-appointment APPOINTMENT_ID` + +* The `APPOINTMENT_ID` refers to the unique identifier assigned by the program (e.g., "A1XX") to an appointment. +* The `APPOINTMENT_ID` can be found in the displayed appointment list when using the list-appointment command. + +Example of usage: + +`delete-appointment A100` + +Expected output: + +
----------------------------------------------------------------------------------------------------
+Appointment A100 is deleted successfully.
+Now you have 0 appointment(s) in the list.
+----------------------------------------------------------------------------------------------------
+
+ +### Sorting appointments: `sort-appointment` +Sorts the appointments in the appointment list. + +Format: `sort-appointment byDate` or `sort-appointment byId` + +* The `sort-appointment byDate` sorts the appointments by date and time in **ascending order**. +* The `sort-appointment byId` sorts the appointments by `APPOINTMENT_ID` in **ascending order**. + +Example of usage: + +* `sort-appointment byDate` +* `sort-appointment byId` + +Expected output of `sort-appointment byDate`: + +
-------------------------------------------Appointments---------------------------------------------
+1. [A101][ ] - S1234567D - 2025-03-31 12:00 PM - Annual checkup
+2. [A102][ ] - S1234567D - 2025-04-30 2:00 PM - Annual checkup
+----------------------------------------------------------------------------------------------------
+
+ +### Marking an appointment as done: `mark-appointment` +Marks a specified appointment as done. + +Format: `mark-appointment APPOINTMENT_ID` + +* The `APPOINTMENT_ID` refers to the unique identifier assigned by the system to an appointment. +* A completed appointment will be marked accordingly in the system. + +Example of usage: + +`mark-appointment A100` + +Expected output: + +
----------------------------------------------------------------------------------------------------
+Appointment A101 is marked successfully.
+----------------------------------------------------------------------------------------------------
+
+ +### Unmarking a completed appointment: `unmark-appointment` +Unmarks a completed appointment, setting it back to pending. + +Format: `unmark-appointment APPOINTMENT_ID` + +* The `APPOINTMENT_ID` must belong to an appointment that has been marked as completed. -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +Example of usage: + +`unmark-appointment A100` + +Expected output: + +
----------------------------------------------------------------------------------------------------
+Appointment A101 is unmarked successfully.
+----------------------------------------------------------------------------------------------------
+
+ +### Finding a patient's appointments: `find-appointment` +Searches for appointments based on the patient's NRIC. + +Format: `find-appointment NRIC` + +* The `NRIC` must match a registered patient’s NRIC in the system. +* All appointments associated with the specified NRIC will be displayed. + +Example of usage: + +`find-appointment S1234567D` + +Expected output: + +
----------------------------------------------------------------------------------------------------
+ Appointments found for NRIC: S1234567D
+ - [A101][ ] - S1234567D - 2025-03-31 12:00 PM - Annual checkup
+ - [A102][ ] - S1234567D - 2025-04-30 2:00 PM - Annual checkup
+----------------------------------------------------------------------------------------------------
+
+ +### Adding a prescription: `add-prescription` + +Adds a new prescription for a patient. + +Format: `add-prescription ic/PATIENT_ID s/SYMPTOMS m/MEDICINES [nt/NOTES]` + +* `PATIENT_ID` must be a valid patient ID in the system +* `SYMPTOMS` is a comma-separated list of symptoms +* `MEDICINES` is a comma-separated list of prescribed medications +* `NOTES` is optional and can contain special instructions + +Example of usage: + +`add-prescription ic/S9876543B s/Fever, Cough m/Paracetamol, Cough syrup nt/Take after meals` + +Expected output: + +
----------------------------------------------------------------------------------------------------
+Successfully added prescription:
+Prescription [S1234567D-1] (2025-04-05 18:52)
+Patient ID: S1234567D
+Symptoms: 
+- Fever
+- Cough
+Medicines: 
+- Paracetamol
+- Cough syrup
+Notes: Take after meals
+
+Prescription has been generated.
+View the prescription for the patient with ID: S1234567D
+and prescription ID: S1234567D-1
+----------------------------------------------------------------------------------------------------
+
+ +### Viewing all prescriptions: `view-all-prescriptions` + +Shows all prescriptions for a specific patient. + +Format: `view-all-prescriptions NRIC` + +`NRIC` must be an existing patient in the system + +Example of Usage: + +`view-all-prescriptions S9876543B` + +Expected output: + +
----------------------------------------------------------------------------------------------------
+Prescriptions for patient John Doe (S1234567D):
+
+Prescription ID: S1234567D-1
+Date: 2025-04-05 18:52
+Symptoms:
+- Fever
+- Cough
+Medicines:
+- Paracetamol
+- Cough syrup
+Notes: Take after meals
+
+Prescription ID: S1234567D-2
+Date: 2025-04-05 19:08
+Symptoms:
+- Sore throat
+Medicines:
+- Cough pills
+Notes: Take 3 times a day
+
+Total prescriptions: 2
+Use 'view-prescription PRESCRIPTION_ID' to view details and generate HTML.
+----------------------------------------------------------------------------------------------------
+
+ +### Viewing specific prescription: `view-prescription` + +Views details of a specific prescription and generates a printable HTML version. + +Format: `view-prescription PRESCRIPTION_ID` + +* `PRESCRIPTION_ID` must be existent in the system +* The HTML file will be generated in the data/prescriptions folder +* Open the HTML file in a web browser to view and print + +Example of usage: + +`view-prescription S9876543B-1` + +Expected output: + +
----------------------------------------------------------------------------------------------------
+Prescription details:
+Prescription [S1234567D-1] (2025-04-05 18:52)
+Patient ID: S1234567D
+Symptoms: 
+- Fever
+- Cough
+Medicines: 
+- Paracetamol
+- Cough syrup
+Notes: Take after meals
+
+Prescription HTML file generated at: C:\Users\Judha Hoka Wishika\Downloads\tp_personal\data\prescriptions\prescription_S1234567D_1.html
+Open this file in a web browser to view and print the prescription.
+----------------------------------------------------------------------------------------------------
+
+ +
+ +### Adding Medical History: `store-history` +Adds one or more entries to a patient's medical history. + +Format: `store-history n/NAME ic/NRIC h/HISTORY_ENTRY1, HISTORY_ENTRY2, ...` + +* The `NRIC` must be of a patient existing in the system. +* Use commas to separate multiple history entries. + +Examples of usage: + +`store-history n/Alex Tan ic/S1234567A h/Coughing, Swelling on left leg` + +Expected output: + +
----------------------------------------------------------------------------------------------------
+Medical history added for John Doe (NRIC: S1234567D).
+----------------------------------------------------------------------------------------------------
+
+ +### Viewing Medical History: `view-history` +Displays medical history of a patient by NRIC or name. + +**Format:** +`view-history NRIC` or +`view-history NAME` + +**Examples:** +- `view-history S1234567A` +- `view-history Alex Tan` + +Expected output: + +
----------------------------------------------------------------------------------------------------
+Medical History for John Doe (NRIC: S1234567D):
+- Diabetes
+- Hypertension`
+- Coughing
+- Swelling on left leg
+----------------------------------------------------------------------------------------------------
+
+ +### Editing Medical History: `edit-history` +Modifies a specific entry in a patient’s medical history. + +Format: `edit-history ic/NRIC old/OLD_HISTORY_ENTRY new/NEW_HISTORY_ENTRY` + +- NRIC must match an existing patient record. +- All history entries are stored as simple strings. +- Viewing by name will display all patients with the given name. +- Editing only replaces the **first matched** old entry. + +Example of usage: + +`edit-history ic/S1234567A old/Diabetes new/Type 2 Diabetes` + +Expected output: + +
+Replaced old history "Diabetes" with "Type 2 Diabetes".
+----------------------------------------------------------------------------------------------------
+Edit-history command executed.
+----------------------------------------------------------------------------------------------------
+
+ +### Exiting the program: `bye` +Exits the program. + +Format: `bye` +
+ +
## FAQ +**Q**: How do I save my data? + +**A**: ClinicEase's data, i.e. patients, appointments and prescriptions, are saved in the hard disk automatically after any command that changes the data. +There is no need to save manually. + +**Q**: Can I edit the data file e.g. `patient_data.txt`? + +**A**: ClinicEase's data are saved automatically as a text file at [your current directory in Command Prompt]/data/[patient_data/appointment_data/prescription_data].txt. +Advanced users are welcome to update data directly by editing that data file. + **Q**: How do I transfer my data to another computer? -**A**: {your answer here} +**A**: You can copy the 'data' folder and paste it in the directory that you save your jar file in another computer. + +> **CAUTION:** +> - If your changes to the data file makes its format invalid, ClinicEase will skip that row. +> - Consequently, that row of data will not be loaded. +> - Therefore, only edit the data file if you are confident in making the correct updates. + +--- + +## Known Issues + +1. **Command-Parameter Parsing Issue** + When users input commands without a space between the command word and parameters (e.g., `add-appointmentic/` instead of `add-appointment ic/`), + the system incorrectly treats this as an unknown command rather than identifying it as a valid command with incorrect formatting. + - **Cause:** + This behavior is due to from the current parser implementation's strict space-delimited tokenization logic in the initial input splitting phase. + - **Impact:** The system cannot recognize the intended valid command, provide appropriate format-suggestion error messages. + - **Current Resolution:** This is an intended design trade-off to maintain parsing consistency, though it may be revisited in future versions. + +--- +
## Command Summary -{Give a 'cheat sheet' of commands here} +| Action | Format, Examples | +|----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Add patient | `add-patient n/NAME ic/NRIC dob/BIRTHDATE g/GENDER p/PHONE a/ADDRESS`
e.g. `add-patient n/John Doe ic/S1234567D dob/1999-12-12 g/M p/98765432 a/123 Main Street h/Diabetes, Hypertension` | +| Delete patient | `delete-patient NRIC`
e.g. `delete-patient S1234567D` | +| Edit patient | `edit-patient ic/NRIC [n/NAME] [dob/BIRTHDATE] [g/GENDER] [a/ADDRESS] [p/PHONE]`
e.g. `edit-patient ic/S1234567D n/Billy Joe dob/1999-12-21` | +| List patient | `list-patient` | +| View patient | `view-patient NRIC`
e.g. `view-patient S1234567D` | +| Store medical history | `store-history n/NAME ic/NRIC h/MEDICAL_HISTORY`
e.g. `store-history n/John Doe ic/S1234567D h/Depression` | +| View medical history | `view-history NRIC` or `view-history NAME`
e.g. `view-history S1234567D` or `view-history John Doe` | +| Edit medical history | `edit-history ic/NRIC old/OLD_TEXT new/NEW_TEXT`
e.g. `edit-history ic/S1234567D old/Depression new/Obesity` | +| Add appointment | `add-appointment ic/NRIC dt/DATE t/TIME dsc/DESCRIPTION`
e.g. `add-appointment ic/S1234567D dt/2025-06-15 t/1400 dsc/Annual Checkup` | +| Delete appointment | `delete-appointment APPOINTMENT_ID`
e.g. `delete-appointment A123` | +| List appointment | `list-appointment` | +| Mark appointment | `mark-appointment APPOINTMENT_ID`
e.g. `mark-appointment A101` | +| Unmark appointment | `unmark-appointment APPOINTMENT_ID`
e.g. `unmark-appointment A101` | +| Sort appointment | `sort-appointment byDate` or `sort-appointment byId` | +| Find appointment | `find-appointment PATIENT_NRIC`
e.g. `find-appointment S1234567D` | +| Add prescription | `add-prescription ic/PATIENT_ID s/SYMPTOMS m/MEDICINES [nt/NOTES]` | +| View all prescriptions | `view-all-prescriptions PATIENT_ID` | +| View specific prescription | `view-prescription PRESCRIPTION_ID` | -* Add todo `todo n/TODO_NAME d/DEADLINE` diff --git a/docs/diagrams/ArchitectureDiagram.png b/docs/diagrams/ArchitectureDiagram.png new file mode 100644 index 0000000000..67253cca02 Binary files /dev/null and b/docs/diagrams/ArchitectureDiagram.png differ diff --git a/docs/diagrams/ArchitectureDiagram.puml b/docs/diagrams/ArchitectureDiagram.puml new file mode 100644 index 0000000000..5735b78785 --- /dev/null +++ b/docs/diagrams/ArchitectureDiagram.puml @@ -0,0 +1,35 @@ +@startuml +actor User + +package "ClinicEase"{ +component UI +component Parser +component Command +component Manager +component Object +component Storage +component Main +} +database FileSystem + +User --> UI +UI ..> Object +Main --> UI +Main ..>Command +Main --> Manager +Main --> Storage +Main ..> Object +Parser ..> Command +Parser ..> Object +Command --> Object +Command ..> UI +Command ..> Manager +Command ..> Storage +Manager --> Object +Manager <..> Storage +Storage ..> Object +Storage ..> Parser +Storage --> FileSystem + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/ArchitectureSequenceDiagram.png b/docs/diagrams/ArchitectureSequenceDiagram.png new file mode 100644 index 0000000000..683a1c951a Binary files /dev/null and b/docs/diagrams/ArchitectureSequenceDiagram.png differ diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml new file mode 100644 index 0000000000..181434a087 --- /dev/null +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -0,0 +1,44 @@ +@startuml + +actor User +participant ":UI" as UI +participant ":Parser" as Parser +participant ":DeletePatientCommand" as Command +participant ":ManagementSystem" as Management +participant ":Storage" as Storage + +User -> UI : "delete-patient S1234567A" +activate UI + +UI -> Parser : parse("delete-patient S1234567A") +activate Parser +Parser -> Parser : parseDeletePatient(userInput) +Parser --> UI : new DeletePatientCommand("S1234567A") +deactivate Parser + +UI -> Command : execute(manager, ui) +activate Command + +Command -> Management : deletePatient("S1234567A") +activate Management + +Management -> Storage : savePatients(patients) +activate Storage +Storage --> Management +deactivate Storage + +Management --> Command : removedPatient +deactivate Management + +Command -> UI : showPatientDeleted(removedPatient, "S1234567A") +activate UI +UI --> Command +deactivate UI + +Command --> UI +deactivate Command + +UI --> User +deactivate UI + +@enduml \ No newline at end of file diff --git a/docs/diagrams/Command.png b/docs/diagrams/Command.png new file mode 100644 index 0000000000..52722af201 Binary files /dev/null and b/docs/diagrams/Command.png differ diff --git a/docs/diagrams/Command.puml b/docs/diagrams/Command.puml new file mode 100644 index 0000000000..2146127e05 --- /dev/null +++ b/docs/diagrams/Command.puml @@ -0,0 +1,184 @@ +@startuml +skinparam componentStyle rectangle +hide circle + +left to right direction +skinparam ranksep 30 +skinparam nodesep 20 + +package "Commands" { + abstract class Command { + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class AddAppointmentCommand { + - appointment : Appointment + +AddAppointmentCommand(appointment : Appointment) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class AddPatientCommand { + - patient : Patient + +AddPatientCommand(patient : Patient) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class AddPrescriptionCommand { + - prescription : Prescription + +AddPrescriptionCommand(prescription : Prescription) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class DeleteAppointmentCommand { + - apptId : String + +DeleteAppointmentCommand(apptId : String) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class DeletePatientCommand { + - nric : String + +DeletePatientCommand(nric : String) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class EditPatientCommand { + - details : String[] + +EditPatientCommand(details : String[]) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class EditPatientHistoryCommand { + - details : String[] + +EditPatientHistoryCommand(details : String[]) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class ExitCommand { + +ExitCommand() + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class FindAppointmentCommand { + - nric : String + +FindAppointmentCommand(nric : String) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class HelpCommand { + +HelpCommand() + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class ListAppointmentCommand { + +ListAppointmentCommand() + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class ListPatientCommand { + +ListPatientCommand() + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class MarkApppointmentCommand { + - apptId : String + +MarkApppointmentCommand(apptId : String) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class SortAppointmentCommand { + - type : String + +SortAppointmentCommand(type : String) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class StoreMedHistoryCommand { + - nric : String + - medHistory : String + +StoreMedHistoryCommand(details : String[]) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class UnmarkAppointmentCommand { + - apptId : String + +UnmarkAppointmentCommand(apptId : String) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class ViewAllPrescriptionsCommand { + - patientId : String + +ViewAllPrescriptionsCommand(patientId : String) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class ViewMedHistoryCommand { + - type : String + - nameOrIc : String + +ViewMedHistoryCommand(details : String[]) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class ViewPatientCommand { + - nric : String + +ViewPatientCommand(nric : String) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + class ViewPrescriptionCommand { + - prescriptionId : String + +ViewPrescriptionCommand(prescriptionId : String) + +execute(manager: ManagementSystem, ui: Ui) : void + +isExit() : boolean + } + + Command <|-- AddAppointmentCommand + Command <|-- AddPatientCommand + Command <|-- AddPrescriptionCommand + Command <|-- DeleteAppointmentCommand + Command <|-- DeletePatientCommand + Command <|-- EditPatientCommand + Command <|-- EditPatientHistoryCommand + Command <|-- ExitCommand + Command <|-- FindAppointmentCommand + Command <|-- HelpCommand + Command <|-- ListAppointmentCommand + Command <|-- ListPatientCommand + Command <|-- MarkApppointmentCommand + Command <|-- SortAppointmentCommand + Command <|-- StoreMedHistoryCommand + Command <|-- UnmarkAppointmentCommand + Command <|-- ViewAllPrescriptionsCommand + Command <|-- ViewMedHistoryCommand + Command <|-- ViewPatientCommand + Command <|-- ViewPrescriptionCommand + + AddAppointmentCommand -[hidden]-> FindAppointmentCommand + AddPatientCommand -[hidden]-> HelpCommand + AddPrescriptionCommand -[hidden]-> ListAppointmentCommand + DeleteAppointmentCommand -[hidden]-> ListPatientCommand + DeletePatientCommand -[hidden]-> MarkApppointmentCommand + EditPatientCommand -[hidden]-> SortAppointmentCommand + EditPatientHistoryCommand -[hidden]-> StoreMedHistoryCommand + ExitCommand -[hidden]-> UnmarkAppointmentCommand +} + +@enduml \ No newline at end of file diff --git a/docs/diagrams/addAppointmentSequence.png b/docs/diagrams/addAppointmentSequence.png new file mode 100644 index 0000000000..e2999127bc Binary files /dev/null and b/docs/diagrams/addAppointmentSequence.png differ diff --git a/docs/diagrams/addAppointmentSequence.puml b/docs/diagrams/addAppointmentSequence.puml new file mode 100644 index 0000000000..fe98dd9d96 --- /dev/null +++ b/docs/diagrams/addAppointmentSequence.puml @@ -0,0 +1,71 @@ +@startuml +hide footbox + +actor User + +participant ":ClinicEase" as ClinicEase +participant ":Parser" as Parser +participant ":Appointment" as Appointment +participant ":AddAppointmentCommand" as AddAppointmentCommand +participant ":ManagementSystem" as ManagementSystem +participant ":Storage" as Storage + +User -> ClinicEase : enter input +activate ClinicEase +ClinicEase -> Parser : parse(input) +activate Parser + +Parser -> Parser : parseAddAppointment(userInput) +activate Parser + +Parser -> Parser : extractValue(userInput) +activate Parser +note right of Parser: extractValue is called separately \n (not in a loop) to parse input and extract each parameter \n: NRIC, date, time, and description +Parser --> Parser : :String +deactivate Parser + +create Appointment +Parser -> Appointment : new Appointment() +activate Appointment +Appointment --> Parser : appt :Appointment +deactivate Appointment + +Parser --> Parser : appt :Appointment +deactivate Parser + + + +create AddAppointmentCommand +Parser -> AddAppointmentCommand : new AddAppointmentCommand(appt) +activate AddAppointmentCommand +AddAppointmentCommand --> Parser : :AddAppointmentCommand +deactivate AddAppointmentCommand +Parser --> ClinicEase : :AddAppointmentCommand +deactivate Parser + +ClinicEase -> AddAppointmentCommand : execute() +activate AddAppointmentCommand +AddAppointmentCommand -> ManagementSystem : addAppointment(appointment) +activate ManagementSystem + +ManagementSystem -> ManagementSystem : findPatientByNRIC() +activate ManagementSystem +ManagementSystem -> Appointment : getNric() +activate Appointment +Appointment --> ManagementSystem : nric :String +deactivate Appointment +ManagementSystem --> ManagementSystem : nric :String +deactivate ManagementSystem + +ManagementSystem -> Storage : saveAppointments() +activate Storage +Storage --> ManagementSystem : +deactivate Storage +ManagementSystem --> AddAppointmentCommand : +deactivate ManagementSystem +AddAppointmentCommand --> ClinicEase : +deactivate AddAppointmentCommand +ClinicEase --> User : display message +deactivate ClinicEase + +@enduml \ No newline at end of file diff --git a/docs/diagrams/addPatientSequence.png b/docs/diagrams/addPatientSequence.png new file mode 100644 index 0000000000..fe5544fa8e Binary files /dev/null and b/docs/diagrams/addPatientSequence.png differ diff --git a/docs/diagrams/addPatientSequence.puml b/docs/diagrams/addPatientSequence.puml new file mode 100644 index 0000000000..636139e5a9 --- /dev/null +++ b/docs/diagrams/addPatientSequence.puml @@ -0,0 +1,60 @@ +@startuml +hide footbox + +participant ":ClinicEase" as ClinicEase +participant ":Parser" as Parser +participant ":AddPatientCommand" as AddPatientCommand +participant ":ManagementSystem" as ManagementSystem +participant ":Patient" as Patient +participant ":Storage" as Storage + +ClinicEase -> Parser : parse(input) +activate Parser + +Parser -> Parser : parseAddPatient(userInput) +activate Parser +Parser -> Parser : extractValue(userInput) +activate Parser +note right of Parser + extractValue is called multiple times\n + to get name, nric, dob, gender,\n + address, and phone +end note +Parser --> Parser : :String[] +deactivate Parser + +create AddPatientCommand +Parser -> AddPatientCommand : new AddPatientCommand(String[]) +activate AddPatientCommand +AddPatientCommand --> Parser +deactivate Parser +Parser --> ClinicEase : :AddPatientCommand +deactivate Parser + +ClinicEase -> AddPatientCommand : execute() +activate AddPatientCommand +AddPatientCommand -> ManagementSystem : addPatient(details) +activate ManagementSystem +ManagementSystem -> ManagementSystem : check if NRIC exists +alt NRIC already exists + ManagementSystem --> AddPatientCommand : Error message +else Else + create Patient + ManagementSystem -> Patient : new Patient(...) + activate Patient + Patient --> ManagementSystem : + deactivate Patient + + ManagementSystem -> ManagementSystem : add patient to HashMap + ManagementSystem -> Storage : savePatients(patients) + activate Storage + Storage --> ManagementSystem : + deactivate Storage + + ManagementSystem --> AddPatientCommand : + deactivate AddPatientCommand + deactivate ManagementSystem +end +AddPatientCommand --> ClinicEase : +deactivate AddPatientCommand +@enduml \ No newline at end of file diff --git a/docs/diagrams/deleteAppointmentSequence.png b/docs/diagrams/deleteAppointmentSequence.png new file mode 100644 index 0000000000..6b7849ebd7 Binary files /dev/null and b/docs/diagrams/deleteAppointmentSequence.png differ diff --git a/docs/diagrams/deleteAppointmentSequence.puml b/docs/diagrams/deleteAppointmentSequence.puml new file mode 100644 index 0000000000..194bf76f38 --- /dev/null +++ b/docs/diagrams/deleteAppointmentSequence.puml @@ -0,0 +1,59 @@ +@startuml +hide footbox + +actor User + +participant ":ClinicEase" as ClinicEase +participant ":Parser" as Parser +participant ":DeleteAppointmentCommand" as DeleteAppointmentCommand +participant ":ManagementSystem" as ManagementSystem +participant ":Appointment" as Appointment +participant ":Storage" as Storage + +User -> ClinicEase : enter input +activate ClinicEase +ClinicEase -> Parser : parse(input) +activate Parser + +Parser -> Parser : parseDeleteAppointment(userInput) +activate Parser +Parser --> Parser : apptId :String +deactivate Parser + +create DeleteAppointmentCommand +Parser -> DeleteAppointmentCommand : new DeleteAppointmentCommand(apptId) +activate DeleteAppointmentCommand +DeleteAppointmentCommand --> Parser : :DeleteAppointmentCommand +deactivate DeleteAppointmentCommand +Parser --> ClinicEase : :DeleteAppointmentCommand +deactivate Parser + +ClinicEase -> DeleteAppointmentCommand : execute() +activate DeleteAppointmentCommand +DeleteAppointmentCommand -> ManagementSystem : deleteAppointment(apptId) + +activate ManagementSystem +ManagementSystem -> ManagementSystem : findPatientByNRIC() +activate ManagementSystem +ManagementSystem -> Appointment : getNric() +activate Appointment +Appointment --> ManagementSystem : nric :String +deactivate Appointment +ManagementSystem --> ManagementSystem : nric :String +deactivate ManagementSystem + +ManagementSystem -> Storage : saveAppointments() +activate Storage +Storage --> ManagementSystem : +deactivate Storage +ManagementSystem --> DeleteAppointmentCommand : +deactivate ManagementSystem + +deactivate ManagementSystem +DeleteAppointmentCommand --> ClinicEase : +deactivate DeleteAppointmentCommand +ClinicEase --> User : display message +deactivate ClinicEase + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/deletePatientSequence.png b/docs/diagrams/deletePatientSequence.png new file mode 100644 index 0000000000..ce3f5b9726 Binary files /dev/null and b/docs/diagrams/deletePatientSequence.png differ diff --git a/docs/diagrams/deletePatientSequence.puml b/docs/diagrams/deletePatientSequence.puml new file mode 100644 index 0000000000..2106a8c6d1 --- /dev/null +++ b/docs/diagrams/deletePatientSequence.puml @@ -0,0 +1,53 @@ +@startuml +hide footbox + +participant ":ClinicEase" as ClinicEase +participant ":Parser" as Parser +participant ":DeletePatientCommand" as DeletePatientCommand +participant ":ManagementSystem" as ManagementSystem +participant ":Storage" as Storage + +ClinicEase -> Parser : parse(input) +activate Parser + +Parser -> Parser : parseDeletePatient(userInput) +activate Parser +Parser --> Parser : :String (nric) +deactivate Parser + +create DeletePatientCommand +Parser -> DeletePatientCommand : new DeletePatientCommand(String) +activate DeletePatientCommand +DeletePatientCommand --> Parser +Parser --> ClinicEase : :DeletePatientCommand +deactivate Parser + +ClinicEase -> DeletePatientCommand : execute() +activate DeletePatientCommand +DeletePatientCommand -> ManagementSystem : deletePatient(nric) +activate ManagementSystem +ManagementSystem -> ManagementSystem : find matching NRIC in list + +alt Patient found + ManagementSystem -> ManagementSystem : remove patient from list + ManagementSystem -> ManagementSystem : remove patient’s appointments + ManagementSystem -> Storage : savePatients(patients) + activate Storage + Storage --> ManagementSystem + deactivate Storage + + ManagementSystem -> Storage : saveAppointments(appointments) + activate Storage + Storage --> ManagementSystem + deactivate Storage + + ManagementSystem --> DeletePatientCommand : deleted Patient +else Patient not found + ManagementSystem --> DeletePatientCommand : null + deactivate ManagementSystem + deactivate DeletePatientCommand +end + +DeletePatientCommand --> ClinicEase : Result (success/failure message) +deactivate DeletePatientCommand +@enduml diff --git a/docs/diagrams/mainComponent.png b/docs/diagrams/mainComponent.png new file mode 100644 index 0000000000..e23265c288 Binary files /dev/null and b/docs/diagrams/mainComponent.png differ diff --git a/docs/diagrams/mainComponent.puml b/docs/diagrams/mainComponent.puml new file mode 100644 index 0000000000..c27df1c638 --- /dev/null +++ b/docs/diagrams/mainComponent.puml @@ -0,0 +1,22 @@ +@startuml +skinparam classAttributeIconSize 0 + +package "Main Component"{ +class ClinicEase { + - ManagementSystem manager + - Ui ui + - Storage storage + + + ClinicEase(filePath: String) + + run(): void + + main(args: String[]): void +} +} + +ClinicEase --> UI +ClinicEase --> Storage +ClinicEase --> ManagementSystem +ClinicEase ..> Command + +hide circle +@enduml \ No newline at end of file diff --git a/docs/diagrams/managerComponent.png b/docs/diagrams/managerComponent.png new file mode 100644 index 0000000000..410201dd3f Binary files /dev/null and b/docs/diagrams/managerComponent.png differ diff --git a/docs/diagrams/managerComponent.puml b/docs/diagrams/managerComponent.puml new file mode 100644 index 0000000000..b5988db00f --- /dev/null +++ b/docs/diagrams/managerComponent.puml @@ -0,0 +1,44 @@ +@startuml +skinparam classAttributeIconSize 0 + +package "Manager Component"{ +class ManagementSystem { + - appointments : List + - patients : List + - prescriptions : List + + + ManagementSystem(List, List) + + ManagementSystem(List, List, List) + + + getPatients() : List + + setAppointments(List) : void + + getAppointments() : List + + addPatient(Patient) : void + + deletePatient(String) : Patient + + viewPatient(String) : Patient + + editPatient(String, String, String, String, String, String) : void + + storeMedicalHistory(String, String) : void + + viewMedicalHistoryByNric(String) : void + + viewMedicalHistoryByName(String) : void + + editPatientHistory(String, String, String) : void + + findPatientByNric(String) : Patient + + addAppointment(Appointment) : void + + deleteAppointment(String) : Appointment + + sortAppointmentsByDateTime(List) : List + + sortAppointmentsById(List) : List + + markAppointment(String) : Appointment + + unmarkAppointment(String) : Appointment + + findAppointmentsByNric(String) : List + + getPrescriptions() : List + + addPrescription(Prescription) : Prescription +} +} + +ClinicEase --> ManagementSystem +ManagementSystem --> Patient +ManagementSystem --> Appointment +ManagementSystem --> Prescription + +hide circle +@enduml + diff --git a/docs/diagrams/objectComponent.png b/docs/diagrams/objectComponent.png new file mode 100644 index 0000000000..f6688f08fd Binary files /dev/null and b/docs/diagrams/objectComponent.png differ diff --git a/docs/diagrams/objectComponent.puml b/docs/diagrams/objectComponent.puml new file mode 100644 index 0000000000..877c8a0c0b --- /dev/null +++ b/docs/diagrams/objectComponent.puml @@ -0,0 +1,89 @@ +@startuml +skinparam classAttributeIconSize 0 + +package "Object Component" { +class Patient { + - id: String + - name: String + - dob: LocalDate + - contactInfo: String + - gender: String + - address: String + - medicalHistory: List + - appointments: List + + + Patient(id, name, dobStr, gender, address, contactInfo, medicalHistory) + + getId(): String + + getName(): String + + getDob(): LocalDate + + getGender(): String + + getAddress(): String + + getContactInfo(): String + + getMedicalHistory(): List + + setName(name: String): void + + setDob(dob: LocalDate): void + + setContactInfo(info: String): void + + setGender(gender: String): void + + setAddress(address: String): void + + getAppointments(): List + + addAppointment(appointment: Appointment): void + + deleteAppointment(apptId: String): void + + toString(): String + + toStringForListView(): String + + toFileFormat(): String +} + +class Appointment { + - id : String + - nric : String + - dateTime : LocalDateTime + - description : String + - isDone : boolean + + + Appointment(nric: String, dateTime: LocalDateTime, description: String) + + Appointment(id: String, nric: String, dateTime: LocalDateTime, description: String) + + getId(): String + + setRunningId(newId: int): void + + getNric(): String + + getDate(): LocalDate + + getTime(): LocalTime + + getDateTime(): LocalDateTime + + getRunningId(): int + + getDescription(): String + + isDone(): boolean + + markAsDone(): void + + unmarkAsDone(): void + + setIsDone(mark: boolean): void + + getStatusIcon(): String + + toString(): String + + toFileFormat(): String +} + +class Prescription { + - patientId : String + - timestamp : LocalDateTime + - prescriptionId : String + - symptoms : List + - medicines : List + - notes : String + + + getPatientId(): String + + getPrescriptionId(): String + + getTimestamp(): LocalDateTime + + getSymptoms(): List + + getMedicines(): List + + getNotes(): String + + toString(): String + + toFileFormat(): String + + static fromFileFormat(String): Prescription + + generateHtml(Patient): String +} + +} + +ManagementSystem --> Patient +ManagementSystem --> Appointment +ManagementSystem --> Prescription + +hide circle +@enduml diff --git a/docs/diagrams/parserClassDiagram.png b/docs/diagrams/parserClassDiagram.png new file mode 100644 index 0000000000..f6ef29bdae Binary files /dev/null and b/docs/diagrams/parserClassDiagram.png differ diff --git a/docs/diagrams/parserClassDiagram.puml b/docs/diagrams/parserClassDiagram.puml new file mode 100644 index 0000000000..60ecb02ee4 --- /dev/null +++ b/docs/diagrams/parserClassDiagram.puml @@ -0,0 +1,52 @@ +@startuml +skinparam classAttributeIconSize 0 +hide circle + +abstract class Command { + + {abstract} execute(manager: ManagementSystem, ui: Ui): void + + {abstract} isExit(): boolean +} + +class Parser { + + {static} parse(input: String): Command + - {static} parseAddPatient(input: String): Patient + - {static} parseDeletePatient(input: String): String + - {static} parseViewPatient(input: String): String + + {static} parseViewHistory(input: String): String[] + + {static} parseStoreHistory(input: String): String[] + - {static} parseAddAppointment(input: String): Appointment + - {static} parseDeleteAppointment(input: String): String + - {static} parseSortAppointment(input: String): String + - {static} parseMarkAppointment(input: String): String + - {static} parseUnmarkAppointment(input: String): String + - {static} parseFindAppointment(input: String): String + - {static} parseEditPatient(input: String): String[] + - {static} parseEditHistory(input: String): String[] + + {static} parseLoadPatient(line: String): Patient + + {static} parseLoadAppointment(line: String): Appointment + + {static} parseAddPrescription(input: String): Prescription + + {static} parseViewAllPrescriptions(input: String): String + + {static} parseViewPrescription(input: String): String + - {static} extractValue(input: String, prefix: String): String +} + +class XYZCommand extends Command { + # commandData: SubData + + execute(manager: ManagementSystem, ui: Ui): void + + isExit(): boolean +} + +class SubData { + - field1: Type + - field2: Type +} + +Parser --> "1" XYZCommand : creates > +Parser --> SubData : creates > + +XYZCommand --> SubData : uses > + +note top of Parser : Parses input into executable commands +note bottom of XYZCommand: - XYZcommand = AddPatientCommand, AddAppointmentCommand, etc... \n- execute and isExit are overridden \n- Constructor: XYZCommand(SubData data) +note right of SubData : - Subdata = Patient, Appointment and Prescription \n- field1 and field2 represent the variables extracted using Parser.extractValue() +@enduml diff --git a/docs/diagrams/parserSequence.png b/docs/diagrams/parserSequence.png new file mode 100644 index 0000000000..7fa74efe26 Binary files /dev/null and b/docs/diagrams/parserSequence.png differ diff --git a/docs/diagrams/parserSequence.puml b/docs/diagrams/parserSequence.puml new file mode 100644 index 0000000000..6ad4f4e482 --- /dev/null +++ b/docs/diagrams/parserSequence.puml @@ -0,0 +1,45 @@ +@startuml +hide footbox + + +participant ":ClinicEase" as ClinicEase +box "Interactions happened in Parser" #F0F0F0 +participant ":Parser" as Parser +participant ":Appointment" as Appointment +participant ":AddAppointmentCommand" as AddAppointmentCommand +end box + +ClinicEase -> Parser : parse("add-appointment ic/...") +activate ClinicEase + +activate Parser +Parser -> Parser : parseAddAppointment(userInput) +activate Parser #lightblue + +Parser -> Parser : extractValue(userInput) +activate Parser #lightgreen +note right of Parser: extractValue is called separately \n (not in a loop) to parse input and extract each parameter \n: NRIC, date, time, and description +Parser --> Parser : :String +deactivate Parser + +create Appointment +Parser -> Appointment : new Appointment() +activate Appointment +Appointment --> Parser : appt :Appointment +deactivate Appointment + +Parser --> Parser : appt :Appointment +deactivate Parser + + +create AddAppointmentCommand +Parser -> AddAppointmentCommand : new AddAppointmentCommand(appt) +activate AddAppointmentCommand +AddAppointmentCommand --> Parser : :AddAppointmentCommand +deactivate AddAppointmentCommand +Parser --> ClinicEase : :AddAppointmentCommand +deactivate Parser +deactivate ClinicEase + + +@enduml \ No newline at end of file diff --git a/docs/diagrams/prescriptionClassDiagram.png b/docs/diagrams/prescriptionClassDiagram.png new file mode 100644 index 0000000000..04fd0cc997 Binary files /dev/null and b/docs/diagrams/prescriptionClassDiagram.png differ diff --git a/docs/diagrams/prescriptionClassDiagram.puml b/docs/diagrams/prescriptionClassDiagram.puml new file mode 100644 index 0000000000..15df428e32 --- /dev/null +++ b/docs/diagrams/prescriptionClassDiagram.puml @@ -0,0 +1,86 @@ +@startuml +skinparam classAttributeIconSize 0 +hide circle + +class Prescription { + - patientId: String + - prescriptionId: String + - timestamp: LocalDateTime + - symptoms: List + - medicines: List + - notes: String + + Prescription(patientId: String, symptoms: List, medicines: List, notes: String) + + Prescription(patientId: String, prescriptionId: String, timestamp: LocalDateTime, symptoms: List, medicines: List, notes: String) + + getPatientId(): String + + getPrescriptionId(): String + + getTimestamp(): LocalDateTime + + getSymptoms(): List + + getMedicines(): List + + getNotes(): String + + toString(): String + + toFileFormat(): String + + {static} fromFileFormat(fileEntry: String): Prescription + + generateHtml(patient: Patient): String +} + +class AddPrescriptionCommand extends Command { + - prescription: Prescription + + AddPrescriptionCommand(prescription: Prescription) + + execute(manager: ManagementSystem, ui: Ui): void + + isExit(): boolean +} + +class ViewPrescriptionCommand extends Command { + - prescriptionId: String + + ViewPrescriptionCommand(prescriptionId: String) + + execute(manager: ManagementSystem, ui: Ui): void + + isExit(): boolean +} + +class ViewAllPrescriptionsCommand extends Command { + - patientId: String + + ViewAllPrescriptionsCommand(patientId: String) + + execute(manager: ManagementSystem, ui: Ui): void + + isExit(): boolean +} + +class ManagementSystem { + - prescriptions: List + + getPrescriptions(): List + + addPrescription(prescription: Prescription): Prescription + + getPrescriptionsForPatient(patientId: String): List + + getPrescriptionById(prescriptionId: String): Prescription +} + +class Parser { + + {static} parseAddPrescription(input: String): Prescription + + {static} parseViewAllPrescriptions(input: String): String + + {static} parseViewPrescription(input: String): String +} + +class Storage { + + {static} savePrescriptions(prescriptions: List): void + + {static} loadPrescriptions(): List + + {static} savePrescriptionHtml(prescription: Prescription, patient: Patient): void +} + +abstract class Command { + + {abstract} execute(manager: ManagementSystem, ui: Ui): void + + {abstract} isExit(): boolean +} + +Patient "1" -- "0..*" Prescription: has > + +ManagementSystem "1" --o "0..*" Prescription: manages > + +AddPrescriptionCommand "1" --o "1" Prescription: creates > +ViewPrescriptionCommand .. ManagementSystem: retrieves prescription from > +ViewAllPrescriptionsCommand .. ManagementSystem: retrieves prescriptions from > + +Parser ..> AddPrescriptionCommand: creates > +Parser ..> ViewPrescriptionCommand: creates > +Parser ..> ViewAllPrescriptionsCommand: creates > + +Storage ..> Prescription: saves/loads > + +@enduml \ No newline at end of file diff --git a/docs/diagrams/prescriptionManagementSequence.png b/docs/diagrams/prescriptionManagementSequence.png new file mode 100644 index 0000000000..895f5af067 Binary files /dev/null and b/docs/diagrams/prescriptionManagementSequence.png differ diff --git a/docs/diagrams/prescriptionManagementSequence.puml b/docs/diagrams/prescriptionManagementSequence.puml new file mode 100644 index 0000000000..b6da5dbf3f --- /dev/null +++ b/docs/diagrams/prescriptionManagementSequence.puml @@ -0,0 +1,76 @@ +@startuml +hide footbox + +actor User + +participant ":ClinicEase" as ClinicEase +participant ":Parser" as Parser +participant ":AddPrescriptionCommand" as AddPrescriptionCommand +participant ":ManagementSystem" as ManagementSystem +participant ":Prescription" as Prescription +participant ":Storage" as Storage + +User -> ClinicEase : enter input +activate ClinicEase +ClinicEase -> Parser : parse(input) +activate Parser + +Parser -> Parser : parseAddPrescription(userInput) +activate Parser +Parser -> Parser : extractValue(userInput) +activate Parser +note right of Parser: extractValue is called multiple times\n to parse patient ID, symptoms, medicines, and notes +Parser --> Parser : :String +deactivate Parser + +create Prescription +Parser -> Prescription : new Prescription(patientId, symptomsList, medicinesList, notes) +activate Prescription +Prescription --> Parser : prescription : Prescription +deactivate Prescription +deactivate Parser + +create AddPrescriptionCommand +Parser -> AddPrescriptionCommand : new AddPrescriptionCommand(prescription) +activate AddPrescriptionCommand +AddPrescriptionCommand --> Parser : :AddPrescriptionCommand +deactivate AddPrescriptionCommand +Parser --> ClinicEase : :AddPrescriptionCommand +deactivate Parser + +ClinicEase -> AddPrescriptionCommand : execute() +activate AddPrescriptionCommand +AddPrescriptionCommand -> ManagementSystem : addPrescription(prescription) +activate ManagementSystem +ManagementSystem -> ManagementSystem : findPatientByNric() +activate ManagementSystem +ManagementSystem --> ManagementSystem : patient : Patient +deactivate ManagementSystem + +alt Patient not found + ManagementSystem --> AddPrescriptionCommand : throw IllegalArgumentException +else Patient found + ManagementSystem -> ManagementSystem : Generate prescription ID + + create Prescription + ManagementSystem -> Prescription : new Prescription(patientId, prescriptionId, timestamp, symptoms, medicines, notes) + activate Prescription + Prescription --> ManagementSystem : newPrescription : Prescription + deactivate Prescription + + ManagementSystem -> Storage : savePrescriptions(prescriptions) + activate Storage + Storage --> ManagementSystem + deactivate Storage + + ManagementSystem --> AddPrescriptionCommand : newPrescription : Prescription +end +deactivate ManagementSystem + +AddPrescriptionCommand -> AddPrescriptionCommand : Display prescription details +AddPrescriptionCommand --> ClinicEase +deactivate AddPrescriptionCommand +ClinicEase --> User : display message +deactivate ClinicEase + +@enduml \ No newline at end of file diff --git a/docs/diagrams/storageComponent.png b/docs/diagrams/storageComponent.png new file mode 100644 index 0000000000..7fb06e5bcb Binary files /dev/null and b/docs/diagrams/storageComponent.png differ diff --git a/docs/diagrams/storageComponent.puml b/docs/diagrams/storageComponent.puml new file mode 100644 index 0000000000..760805bc1d --- /dev/null +++ b/docs/diagrams/storageComponent.puml @@ -0,0 +1,44 @@ +@startuml +skinparam componentStyle rectangle +hide circle + +package "Storage Component" { + + class Storage { + - directoryPath : String + - patientFilePath : String + - appointmentFilePath : String + - prescriptionFilePath : String + - prescriptionDirPath : String + - patients : Patient + + + Storage(directory : String) + + savePatients(patientList : List) : void + + loadPatients() : List + + saveAppointments(appointmentList : List) : void + + loadAppointments(system : ManagementSystem) : List + + savePrescriptions(prescriptionList : List) : void + + loadPrescriptions() : List + + savePrescriptionHtml(prescription : Prescription, patient : Patient) : void + } + + } + + package "Objects" { + class Patient + class Appointment + class Prescription + } + + package "Utils" { + class Parser + class ManagementSystem + } + + Storage ..> Patient + Storage ..> Appointment + Storage ..> Prescription + Storage ..> Parser + Storage <..> ManagementSystem + +@enduml \ No newline at end of file diff --git a/docs/diagrams/storeMedicalHistorySequence.png b/docs/diagrams/storeMedicalHistorySequence.png new file mode 100644 index 0000000000..2e7cf05006 Binary files /dev/null and b/docs/diagrams/storeMedicalHistorySequence.png differ diff --git a/docs/diagrams/storeMedicalHistorySequence.puml b/docs/diagrams/storeMedicalHistorySequence.puml new file mode 100644 index 0000000000..cc9e3f1fed --- /dev/null +++ b/docs/diagrams/storeMedicalHistorySequence.puml @@ -0,0 +1,54 @@ +@startuml +hide footbox + +participant ":ClinicEase" as ClinicEase +participant ":Parser" as Parser +participant ":StoreMedHistoryCommand" as StoreMedHistoryCommand +participant ":ManagementSystem" as ManagementSystem +participant ":Patient" as Patient +participant ":Storage" as Storage + +ClinicEase -> Parser : parse(input) +activate Parser + +Parser -> Parser : parseStoreHistory(userInput) +activate Parser +Parser -> Parser : extractValue(userInput) +activate Parser +note right of Parser + extractValue is called multiple times + to parse input and extract parameters +end note +Parser --> Parser : :String +deactivate Parser + +create StoreMedHistoryCommand +Parser -> StoreMedHistoryCommand : new StoreMedHistoryCommand(String[]) +activate StoreMedHistoryCommand +StoreMedHistoryCommand --> Parser : :StoreMedHistoryCommand +deactivate StoreMedHistoryCommand +Parser --> ClinicEase : :StoreMedHistoryCommand +deactivate Parser + +ClinicEase -> StoreMedHistoryCommand : execute() +activate StoreMedHistoryCommand +StoreMedHistoryCommand -> ManagementSystem : storeMedicalHistory(name, nric, medHistory) +activate ManagementSystem +ManagementSystem -> ManagementSystem : findPatientByNric(nric) +activate ManagementSystem +ManagementSystem -> Patient : getMedicalHistory() +activate Patient +Patient --> ManagementSystem : returns List +deactivate Patient +deactivate ManagementSystem + +ManagementSystem -> Storage : savePatients(patients) +activate Storage +Storage --> ManagementSystem : +deactivate Storage +ManagementSystem --> StoreMedHistoryCommand : +deactivate ManagementSystem +StoreMedHistoryCommand -> ClinicEase : +deactivate StoreMedHistoryCommand + +@enduml diff --git a/docs/diagrams/uiComponent.png b/docs/diagrams/uiComponent.png new file mode 100644 index 0000000000..10ef43302e Binary files /dev/null and b/docs/diagrams/uiComponent.png differ diff --git a/docs/diagrams/uiComponent.puml b/docs/diagrams/uiComponent.puml new file mode 100644 index 0000000000..3a7c15bfb4 --- /dev/null +++ b/docs/diagrams/uiComponent.puml @@ -0,0 +1,33 @@ +@startuml +skinparam classAttributeIconSize 0 + +package "UI Component" { + class UI { + - Scanner sc + + Ui() + + showWelcome(): void + + showBye(): void + + showLine(): void + + readCommand(): String + + showError(message: String): void + + showHelp(): void + + showPatientAdded(patients: List): void + + showPatientDeleted(removedPatient: Patient, nric: String): void + + showPatientViewed(matchedPatient: Patient, nric: String): void + + showPatientList(patients: List): void + + showPatientHistory(patient: Patient): void + + showAppointmentAdded(appointments: List): void + + showAppointmentDeleted(appointments: List, removedAppointment: Appointment, apptId: String): void + + showAppointmentMarked(appointments: List, markedAppointment: Appointment, apptId: String): void + + showAppointmentUnmarked(appointments: List, markedAppointment: Appointment, apptId: String): void + + showAppointmentList(appointments: List): void + + showAppointmentsFound(appointments: List, nric: String): void + } + +} + +UI ..> Object +Main --> UI + +hide circle +@enduml diff --git a/docs/diagrams/viewPatientSequence.png b/docs/diagrams/viewPatientSequence.png new file mode 100644 index 0000000000..474b9e47a3 Binary files /dev/null and b/docs/diagrams/viewPatientSequence.png differ diff --git a/docs/diagrams/viewPatientSequence.puml b/docs/diagrams/viewPatientSequence.puml new file mode 100644 index 0000000000..3c4d06d409 --- /dev/null +++ b/docs/diagrams/viewPatientSequence.puml @@ -0,0 +1,60 @@ +@startuml +hide footbox + +actor User + +participant ":ClinicEase" as ClinicEase +participant ":UI" as UI +participant ":Parser" as Parser +participant ":ViewPatientCommand" as ViewPatientCommand +participant ":ManagementSystem" as ManagementSystem +participant ":Patient" as Patient + +User -> ClinicEase : enter input +activate ClinicEase + +ClinicEase -> UI : readCommand() +activate UI + +UI --> ClinicEase : input +deactivate UI +ClinicEase -> Parser : parse(input) +activate Parser +Parser -> Parser : parseViewPatient(userInput) +activate Parser +Parser -> Parser : extractValue(userInput) +activate Parser +deactivate Parser +Parser --> Parser : nric :String +deactivate Parser + +Parser --> ClinicEase : ViewPatientCommand + +deactivate Parser + +ClinicEase -> ViewPatientCommand : execute() +activate ViewPatientCommand + +ViewPatientCommand -> ManagementSystem : viewPatient(nric) +activate ManagementSystem + +ManagementSystem -> Patient : getPatient(nric) +activate Patient + +Patient --> ManagementSystem : matchedPatient +deactivate Patient + +ManagementSystem --> ViewPatientCommand +deactivate ManagementSystem + +ViewPatientCommand -> UI : showPatientViewed(matchedPatient) +deactivate ViewPatientCommand +activate UI + +UI --> ClinicEase +deactivate UI + +ClinicEase --> User +deactivate ClinicEase + +@enduml diff --git a/docs/team/basudeb2005.md b/docs/team/basudeb2005.md new file mode 100644 index 0000000000..f84b42c53e --- /dev/null +++ b/docs/team/basudeb2005.md @@ -0,0 +1,94 @@ +# Basudeb Chakraborty - Project Portfolio Page + +## Overview +ClinicEase is a desktop app with a command-line interface for doctors to manage patients, appointments, and prescriptions. It allows tracking medical histories, scheduling, and exporting detailed prescriptions in HTML format. + +## Summary of Contributions + +### Code contributed +[RepoSense Code Dashboard](https://nus-cs2113-ay2425s2.github.io/tp-dashboard/?search=&sort=groupTitle%20dsc&sortWithin=title&since=2025-02-21&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=Basudeb2005&tabRepo=AY2425S2-CS2113-T11b-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +Full URL: [https://nus-cs2113-ay2425s2.github.io/tp-dashboard/?search=&sort=groupTitle%20dsc&sortWithin=title&since=2025-02-21&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=Basudeb2005&tabRepo=AY2425S2-CS2113-T11b-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false](https://nus-cs2113-ay2425s2.github.io/tp-dashboard/?search=&sort=groupTitle%20dsc&sortWithin=title&since=2025-02-21&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=Basudeb2005&tabRepo=AY2425S2-CS2113-T11b-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +### Enhancements implemented + +#### 1. Core Application Structure +I designed and implemented the foundational structure of the application, including: +- Initial UI class setup with welcome/goodbye messages and command reading functionality +- Parser framework for processing user commands +- Basic application workflow in ClinicEase main class + +#### 2. Appointment Management System +I implemented key appointment features: +- Appointment class design with unique IDs and status tracking +- Add appointment command to schedule patient appointments +- Delete appointment functionality to remove unnecessary appointments +- Appointment display functionality in UI with proper formatting + +#### 3. Prescription Management System +I implemented the complete Prescription Management System, a key feature that enables doctors to create and manage patient prescriptions in the ClinicEase application: +- **Prescription Class Design**: Designed and implemented the `Prescription` class with essential attributes and functionality: + - Storage of patient details, symptoms, medicines, and notes + - Automatic generation of unique prescription IDs + - Timestamp recording for each prescription + +- **Prescription Commands**: Implemented three main commands for managing prescriptions: + - `add-prescription`: Allows doctors to create new prescriptions with patient ID, symptoms, medicines, and optional notes + - `view-all-prescriptions`: Shows all prescriptions for a specific patient + - `view-prescription`: Displays details of a specific prescription by ID + +- **HTML Generation**: Created a feature that generates HTML prescription documents for printing: + - Professional layout with CSS styling + - Organized sections for patient information, symptoms, medicines, and special instructions + - Print functionality via a button in the HTML document + +- **Storage and Persistence**: Implemented storage functionality to save and retrieve prescriptions from the file system with a custom serialization format. + +- **Comprehensive Testing**: Created an extensive test suite for the prescription subsystem: Unit tests for all prescription-related classes, Integration tests for prescription management functionality, End-to-end testing of the prescription workflow. + +### Contributions to the User Guide + +- Added the "Managing Prescriptions" section, documenting: + - Command format and examples for adding prescriptions + - Instructions for viewing all prescriptions for a patient + - Instructions for viewing specific prescriptions and generating HTML reports + - Command summary section for all prescription-related commands + +### Contributions to the Developer Guide + +- Added documentation for the core application structure +- Added documentation for the prescription management features, including Use cases for adding, viewing, and generating prescriptions, Manual testing instructions for prescription management, and User stories related to prescription functionality. + +### Contributions to team-based tasks + +- Set up the initial application architecture that the team built upon +- Integrated the prescription management subsystem with the existing patient management system and ensured compatibility of prescription commands with the overall parsing structure + +### Features I'm proud of + +1. **HTML Prescription Generation** + - The ability to generate professional-looking prescriptions that doctors can print was a feature I'm particularly proud of implementing. This bridges the gap between digital management and the physical documents patients need. + +2. **Comprehensive Prescription Workflow** + - Creating a complete end-to-end solution that handles everything from prescription creation to storage to retrieval to presentation was a significant achievement. + +3. **Robust Testing Framework** + - The extensive test suite I developed ensures the reliability of the prescription system, with test coverage for all major components and operations. + +4. **Technical Documentation** + - The detailed sequence and class diagrams help future developers understand the architecture and implementation of the prescription management feature. + +5. **Architecture Documentation Blueprint** + - Created initial architecture diagram + - Created the sequence diagrams + - Collaborated with the team to refine and evolve the architecture diagram + +### Issues Resolved + +1. **Better User Experience** + - Fixed vague error messages for prescription commands + - Provided clearer, more intuitive feedback + +2. **Prescription Storage Bug Fixed** + - Resolved "Index out of bounds" error when loading over 5 prescriptions + - Enabled support for unlimited entries and prevented data loss \ No newline at end of file diff --git a/docs/team/chwenyee.md b/docs/team/chwenyee.md new file mode 100644 index 0000000000..4d06050d15 --- /dev/null +++ b/docs/team/chwenyee.md @@ -0,0 +1,88 @@ +# Wen Yee's Project Portfolio Page + +## Project: ClinicEase + +**ClinicEase** is a CLI-based clinic management program that helps doctors - our target users - to efficiently manage patient records, +medical histories, and appointments. The system enables user to view and update patient details, +manage appointments, maintain medical history records, and manage prescriptions. +This project aims to simplify clinic administrative tasks with a user-friendly interface. + +## Summary of Contributions +Code Dashboard Link: [Wen Yee's Code Dashboard](https://nus-cs2113-ay2425s2.github.io/tp-dashboard/?search=chwenyee&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2025-02-21&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +### Enhancement Implemented + +1. Reformat the UI output to ensure a consistent and aligned display format across the program. +2. Introduced custom exceptions e.g. `DuplicatePatientIDException`, `UnknownCommandException` and `InvalidInputFormatException` +in v1.0 to enable more graceful error handling. +3. Improved the logic of `Parser.extractValue()` to correctly extract user input parameters, especially when both `dt/` and `t/` +are present. +4. Restructured the code in `ClinicEase.java` and `Parser.java`, and created a `command` package to make the codebase more OOP after v1.0. +5. Linked `Appointment` Class to `Patient` Class to ensure appointments stored in a `Patient` object are kept in sync +with the appointment list in `ManagementSystem`. +6. Changed the data type of `dateTime` variable in `Appointment` to be `LocalDateTime` for better input validation of +`dt/` and `t/`, and to simplify the implementation of the `sort-appointment` feature. +7. Added comprehensive validation for: NRIC format compliance, appointment scheduling conflicts (throws `AppointmentClashException`) +and future-dated appointments (prevents past-date/time entries) +8. Implemented cleanup of associated appointment records when the patient is deleted. +9. Added JavaDoc for files such as AddAppointmentCommand, DeleteAppointmentCommand, ListAppointmentCommand, SortAppointmentCommand, +Appointment, ExitCommand, HelpCommand, and Parser. + +#### Features Implemented +- `add-appointment`: Adds a new appointment. +- `delete-appointment`: Deletes a specified appointment. +- `sort-appointment`: Sorts appointments by `byDate` or `byId`. +- `help`: Lists all available commands. + +#### Contributions to the UG: +- Wrote sections: Table of Contents, Quick Start, Features (`help`, `add-appointment`, `delete-appointment`, `sort-appointment`, `bye`). +- Added "NOTES" on feature's command syntax, parameters (`NRIC`, `DATE`, `TIME`, `APPOINTMENT_ID`). +- Wrote FAQ: data saving, data transfer, risks of editing files. +- Documented Known Issue: space omission in command parsing. +- Wrote Command Summary table with formats & examples of usage. + +#### Contributions to the DG: +- Documented `Parser` component design: + - Wrote explanation of parsing workflow. + - Created diagrams: `parserClassDiagram.png`, `parserSequence.png`. +- Add & Delete Appointment Features: + - Documented implementation details & usage scenarios. + - Diagrams: `addAppointmentSequence.png`, `deleteAppointmentSequence.png`. + - Explained rationale & rejected alternatives. +- Other sections: Table of Contents, Non-functional Requirements, Glossary. +- Non-functional requirements +- Glossary + +#### Contributions to team-based tasks: + +- Setting up the Github team repo +- Maintaining the issue tracker e.g. issue and milestones +- Release management e.g. wrapping up milestones, saving DG and UG as PDFs + +#### Review/mentoring contributions: + +- Helped teammates resolve Git merge conflicts and guided them on pull request practices. +- Reminding teammates of important deadlines. +- Helped to test out the newly-implemented features to ensure the feature works as desired. +- Discussed with teammates on what to include for Design and Implementation section in Developer Guide. +- Helped review the coding style consistency + +
+ +#### Contributions to the Developer Guide + +**Diagrams I contributed:** + +Parser Class Diagram: +![parser-class-diagram](../diagrams/parserClassDiagram.png) + +Parser Sequence Diagram: +![parser-sequence-diagram](../diagrams/parserSequence.png) + +Add Appointment Sequence Diagram: +![add-appointment](../diagrams/addAppointmentSequence.png) + +Delete Appointment Sequence Diagram: +![delete-appointment](../diagrams/deleteAppointmentSequence.png) + + diff --git a/docs/team/dylancmznus.md b/docs/team/dylancmznus.md new file mode 100644 index 0000000000..a1d783bff1 --- /dev/null +++ b/docs/team/dylancmznus.md @@ -0,0 +1,97 @@ +# Dylan's Project Portfolio Page + +## Project: ClinicEase + +**ClinicEase** is a CLI-based clinic management application that allows users to efficiently manage patient records, medical histories, and appointments. The system provides commands for viewing and updating patient details, managing appointments, and maintaining medical history records. This project aims to simplify clinic administrative tasks with a user-friendly interface. + +Given below are my contributions to the project. + +--- + +### New Feature: +**`view-patient`**: +- Allows users to view a patient's personal details via their NRIC (e.g., `view-patient ic/S1234567A`). +- Displays the patient's full name, contact details, and other personal information. +- Ensures that the provided NRIC matches an existing patient before retrieving the data. + +### New Feature: + +**`find-appointment`**: +- Finds and lists all appointments for a patient using their NRIC (e.g., `find-appointment ic/S1234567A`). +- Retrieves a list of appointments with the specified patient. + +### New Feature: +**`mark-appointment`**: +- Allows users to mark an appointment as completed for a specific patient using their NRIC and the appointment ID (e.g., `mark-appointment ic/S1234567A id/001`). +- Verifies that the patient exists and that the appointment ID corresponds to an existing appointment before updating the status. + +### New Feature: +**`unmark-appointment`**: +- Unmarks a completed appointment, changing its status as uncompleted (e.g., `unmark-appointment ic/S1234567A id/001`). + +--- + +### Code Contributed: + +**Code Dashboard Link**: [Click here]() + +--- + +### Enhancements Implemented: + +**Parser Integration** + - Implemented some parsing logic (`parseViewPatient`, `parseMarkAppointment`, `parseUnmarkAppointment`, `parseFindAppointment`) to handle new command formats. + - Ensured error messages provide clarity for invalid commands or incorrect command parameters (e.g., `InvalidInputFormatException`). + +**Persistence and Data Integrity** + - Enhanced the `ManagementSystem` to: + - Verify the existence of the patient and appointments before marking or unmarking. + - Ensure that changes to appointment statuses are reflected persistently in the system. + +**Exception Handling** + - Improved user feedback for invalid commands or missing parameters, particularly for appointment management. + +--- + +### User Guide (UG) + +- **Sections**: + - [Viewing Patient Information: `view-patient`](#) + - [Marking Appointments: `mark-appointment`](#) + - [Unmarking Appointments: `unmark-appointment`](#) + - [Finding Appointments: `find-appointment`](#) + + I authored the instructions, formats, and examples for these commands, providing clarification on valid NRIC formats and how to use appointment IDs for marking and unmarking. + +--- + +### Developer Guide (DG) + +1. **Use Cases for Essential and Additional Features** + - Authored the use cases for essential commands like: + - `add-patient` + - `delete-patient` + - `add-appointment` + - `edit-patient` + - `sort-appointment` + - Provided detailed step-by-step scenarios for users to understand how these features should be used to interact with the system. + +2. **Implementation and UML Diagrams** + - Created **Sequence Diagrams** for the `view-patient` feature, demonstrating how the command interacts with the `Parser`, `ManagementSystem`, and `Storage` components. + - Updated UML diagrams to reflect the data flow and validation steps involved in retrieving patient information using NRIC. + +3. **Components and Class Diagrams** + - Created **Class Diagrams** for the Manager Component and Object Component. + - Provided details about these components in the Architecture Diagram,. + +--- + +### Community Involvement + +- Conducted code reviews for pull requests affecting appointment management, particularly focusing on ensuring that appointments are validated correctly and updates are persisted in storage. +- Assisted teammates in resolving command parsing issues and clarified how appointments are managed and retrieved in the system. +- Ensured adherence to Java coding conventions across the codebase, especially in method naming and parameter validation. +- Suggested improvements for better user experience, such as providing clearer feedback on appointment status updates. +- Contributed ideas for expanding the appointment feature, such as introducing appointment reminders or notifications. + +--- diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/judhoka.md b/docs/team/judhoka.md new file mode 100644 index 0000000000..c8d8b0999b --- /dev/null +++ b/docs/team/judhoka.md @@ -0,0 +1,113 @@ +# Judha's Project Portfolio Page + +## Project: ClinicEase + +**ClinicEase** is a CLI-based clinic management system that allows users to manage patient records, appointments, and prescriptions efficiently. The application streamlines administrative workflows in clinics by offering reliable, user-friendly, and persistent command-line interactions. + +My main contribution in features was to design and implement patient related features (`add-patient`, `delete-patient`, etc...) to ensure that users can store patient data properly with their respective credentials. I also mainly contributed in creating the storage system for the entire system to ensure that data is properly kept and saved. + +--- + +## Code Contributed + +**Code Dashboard Link**: [Click here to see my code contribution](https://nus-cs2113-ay2425s2.github.io/tp-dashboard/?search=judhoka&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2025-02-21) + +--- + +## Features Implemented + +### [`add-patient`](../UserGuide.md/#adding-a-new-patient--add-patient) +- Allows users to register a new patient by specifying fields such as name, NRIC, date of birth, gender, phone number, and address. +- Includes validation to prevent duplicate NRIC entries, ensuring each patient is uniquely identified. +- Example: + `add-patient n/John Doe ic/S1234567A dob/1990-01-01 g/M p/98765432 a/123 Main St` + +### [`delete-patient`](../UserGuide.md/#deleting-a-patient--delete-patient) +- Enables the removal of a patient using their NRIC. +- Validates whether the NRIC exists in the system before attempting deletion, and provides informative error messages if not found. +- Upon successful deletion, confirms the action and removes any associated appointments and prescription references. + +### [`list-patient`](../UserGuide.md/#listing-all-patients-list-patient) +- Displays all patients currently stored in the system in a numbered and neatly formatted list. +- Includes name, NRIC, and other key attributes in a concise format. +- Improves usability with consistent dividers and spacing for readability. +- Helpful in providing an overview of all registered patients before performing operations like `delete-patient` or `view-patient`. + +### [`view-patient`](../UserGuide.md/#viewing-patient-details-view-patient) +- Shows full details of a selected patient, including: + - Name, NRIC, date of birth, gender, phone number, and address. + - Optionally, linked appointments and prescriptions (if supported by system design). +- Supports lookup by NRIC (e.g., `view-patient ic/S1234567A`), with input validation and out-of-bounds handling. +- Designed to be clear and informative, acting as a one-stop overview of a patient's data. + +--- + +## Parser Integration + +- Developed parser logic for all implemented commands and storage system. +- Handled command recognition, parameter extraction, and error messaging. +- Validated argument formats and threw custom exceptions like `InvalidInputFormatException`. + +--- + +## Storage System + +- Designed and implemented the file-based persistent `Storage` class. +- Saved and loaded: + - Patient data from `patient_data.txt` + - Appointment data from `appointment_data.txt` + - Prescription data from `prescription_data.txt` +- Linked appointments to patients during data load. +- Generated HTML versions of prescriptions for viewing and printing. +- Ensured initialization safety via `UnloadedStorageException`. + +--- + +## Exception Handling + +- Designed exception handling framework with custom messages. +- Threw descriptive exceptions when: + - File paths were missing. + - Data parsing failed. + - Commands were malformed or incomplete. +- Used try-catch in the main loop to ensure graceful error handling. + +--- + +## Testing + +- Wrote JUnit test cases for parser logic, patient model, and storage. +- Threw various types of exceptions (e.g, `UnloadedStorageException`) if data writing fails, allowing the system to safely abort the operation. +- Manually tested combinations of valid and invalid inputs. + +--- + +## User Guide (UG) + +- Contributed to documentation for all implemented commands. +- Provided consistent format, expected outputs, and usage examples. +- Documented edge cases and failure scenarios for better UX. + +--- + +## Developer Guide (DG) + +- Authored complete component sections: + - Architecture + - `UI` component + - `Storage` component + - `Main` component +- Explained design considerations, structure, and responsibilities. +- Created and updated UML diagrams (Architecture, Class, and Sequence). +- Maintained consistency in formatting, phrasing, and layout across the DG. + +--- + +## Community Involvement + +- Reviewed teammate pull requests, particularly those related to parser, patient, appointment and storage system. +- Proposed improvements for error messaging and user experience. +- Ensured coding style consistency (naming, spacing, error format). +- Participated in team sync-ups to align features, naming conventions, and architecture decisions. + +--- diff --git a/docs/team/jyukuan.md b/docs/team/jyukuan.md new file mode 100644 index 0000000000..ef269d351d --- /dev/null +++ b/docs/team/jyukuan.md @@ -0,0 +1,99 @@ +# Project Portfolio Page (PPP) + +## Overview + +**ClinicEase** is a CLI-based clinic management application that enables users to handle patient records, medical +histories, and appointments quickly and effectively. It provides commands for adding, editing, and viewing patient +details, as well as storing and updating their medical histories. This project streamlines a clinic’s administrative +tasks in a single, user-friendly CLI system. + +I have been primarily involved in designing and implementing the **patient’s history** functionality to ensure users can +store, view, and edit patients’ medical histories with robust validation and persistence. + +--- + +## Summary of Contributions + +### 1. Code Contributed + +- **Code Dashboard Link + **: [Click here to see my code contribution]() + +### 2. Enhancements Implemented + +1. **Medical History Commands** + - **`store-history`**: + - Allows users to add new medical history entries to an existing patient’s record. + - Accepts multiple comma-separated entries for one command (e.g., `h/Diabetes, Hypertension`). + - Performs checks to ensure the patient exists, preventing invalid input from updating records. + - **`view-history`**: + - Supports viewing a patient’s medical history by either NRIC (e.g., `view-history ic/S1234567A`) or by name ( + e.g., `view-history John Doe`). + - Displays all stored entries for the patient(s) found. + - **`edit-history`**: + - Enables replacing the first occurrence of a specified old history entry with a new one. + - Uses `equalsIgnoreCase()` matching, providing leniency in text formatting. + +2. **Parser Integration** + - Implemented specialized parsing logic (`parseStoreHistory`, `parseViewHistory`, `parseEditHistory`) to handle the + format for new commands. + - Ensured error messages (e.g., `InvalidInputFormatException`) provide clarity when commands are malformed. + +3. **Exception Handling** + - Enhanced user feedback for invalid commands or missing parameters. + - Threw `UnloadedStorageException` if data writing fails, allowing the system to safely abort the operation. + +4. **Java Doc** + - Provided code reviews on pull requests that impacted the data structures for storing patient details and medical + histories. + - Authored detailed JavaDoc for classes and methods dealing with patient history (`store-history`, `view-history`, + `edit-history` commands, and related `ManagementSystem` methods). This documentation highlights parameters, return + values, and exceptions, making the code easier to read and maintain. + - Helped maintain consistent Java coding conventions (e.g., method naming, usage of final variables) throughout the + codebase. + +5. **Review / Mentoring Contributions** + + - Provided code reviews on pull requests that impacted the data structures storing patient details. + - Helped maintain consistent Java coding conventions (e.g., method naming, usage of final variables) throughout the + codebase. + +### 3. Contributions to the User Guide (UG) + +- **Sections**: + - [Adding Medical History: `store-history`](#) + - [Viewing Medical History: `view-history`](#) + - [Editing Medical History: `edit-history`](#) + + I wrote the instructions, formats, and examples for these commands. I also included **tips** about commas for multiple + entries and clarifications on the usage of NRIC vs. name. + +### 4. Contributions to the Developer Guide (DG) + +1. **Testing Patient Management Features** + - Authored the guidelines and scenarios on how to manually test and validate patient-related operations. + - Added use cases and step-by-step instructions to ensure testers can verify correctness for storing, viewing, and + editing patient history. + +2. **Storing Medical History Feature** + - Wrote the detailed explanation of how `store-history`, `view-history`, and `edit-history` commands work + internally. + - Added or updated sequence diagrams in PlantUML to illustrate the flow of data and command execution for medical + history. + +3. **UML Diagrams** + - Created the **Sequence Diagrams** for the `store-history` feature, demonstrating how the command interacts with + `Parser`, `ManagementSystem`, and `Storage`. + - Ensured diagrams accurately reflect the final implementation (parameter extraction, validation steps, and + file-saving routines). + - Created the **Class Diagrams** for Command Component. + - Ensured diagrams accurately reflect the final implementation. + +### 5. Contributions to Team-Based Tasks + +- Participated in **team discussions** to decide on command naming patterns (`store-history` vs. `add-history`) and the approach to handle multiple comma-separated entries. +- Ensured consistent **exception messages** across the project for invalid data entries. +- Reviewed and tested merges from teammates, particularly those that affected `ManagementSystem` or `Parser`. + +--- + diff --git a/src/main/java/ClinicEase.java b/src/main/java/ClinicEase.java new file mode 100644 index 0000000000..c496629116 --- /dev/null +++ b/src/main/java/ClinicEase.java @@ -0,0 +1,93 @@ +import command.Command; +import exception.AppointmentClashException; +import exception.DuplicatePatientIDException; +import exception.InvalidInputFormatException; +import exception.PatientNotFoundException; +import exception.UnknownCommandException; +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import manager.Patient; +import manager.Appointment; +import manager.Prescription; +import miscellaneous.Parser; +import miscellaneous.Ui; +import storage.Storage; + +import java.util.ArrayList; +import java.util.List; + +/** + * Main class of the ClinicEase application, responsible for initializing + * components and driving the command execution loop. + * ClinicEase is a desktop application designed for clinic management, + * providing a command-line interface for managing patients, appointments, + * and prescriptions. + */ +public class ClinicEase { + + private ManagementSystem manager; + private Ui ui; + private Storage storage; + + /** + * Constructs a new ClinicEase application with a specified storage location. + * Initializes the UI, storage, and management system components. + * Attempts to load existing data from storage files if available. + * + * @param filePath The directory path where data files will be stored + */ + public ClinicEase(String filePath) { + assert filePath != null : "File path cannot be null"; + this.ui = new Ui(); + this.storage = new Storage(filePath); + + try { + List patients = Storage.loadPatients(); + List prescriptions = Storage.loadPrescriptions(); + this.manager = new ManagementSystem(patients, new ArrayList<>(), prescriptions); + + List appointments = Storage.loadAppointments(manager); + manager.setAppointments(appointments); + } catch (UnloadedStorageException e) { + ui.showError("Could not load data: " + e.getMessage()); + this.manager = new ManagementSystem(new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + } + } + + /** + * Starts the application's main execution loop. + * Displays welcome message, continuously reads user commands, + * executes them until an exit command is received. + * Handles exceptions by displaying appropriate error messages. + */ + public void run() { + ui.showWelcome(); + boolean running = true; + + while (running) { + try { + String input = ui.readCommand(); + if (input.isEmpty()) { + continue; + } + Command command = Parser.parse(input); + command.execute(manager, ui); + running = !command.isExit(); + } catch (InvalidInputFormatException | UnknownCommandException | DuplicatePatientIDException | + UnloadedStorageException | PatientNotFoundException | AppointmentClashException e) { + ui.showError(e.getMessage()); + } + } + } + + /** + * The entry point of the application. + * Creates a new ClinicEase instance with storage in the "data" directory + * and starts the application. + * + * @param args Command line arguments (not used) + */ + public static void main(String[] args) { + new ClinicEase("data").run(); + } +} diff --git a/src/main/java/command/AddAppointmentCommand.java b/src/main/java/command/AddAppointmentCommand.java new file mode 100644 index 0000000000..27153080f7 --- /dev/null +++ b/src/main/java/command/AddAppointmentCommand.java @@ -0,0 +1,51 @@ +package command; + +import exception.AppointmentClashException; +import exception.PatientNotFoundException; +import exception.UnloadedStorageException; +import manager.Appointment; +import manager.ManagementSystem; +import miscellaneous.Ui; + +/** + * Represents a command to add a new appointment to the management system. + */ +public class AddAppointmentCommand extends Command { + + protected Appointment appointment; + + /** + * Constructs an AddAppointmentCommand with the specified appointment. + * + * @param appointment The appointment to be added to the system. + */ + public AddAppointmentCommand(Appointment appointment) { + this.appointment = appointment; + } + + /** + * Gets the appointment associated with this command. + * + * @return The appointment to be added. + */ + public Appointment getAppointment() { + return appointment; + } + + /** + * Executes the command to add the appointment to the appointment list in management system. + * + * @param manager The management system that stores the appointment. + * @param ui The user interface for displaying messages. + * @throws UnloadedStorageException If the storage component is not properly loaded. + * @throws PatientNotFoundException If the patient associated with the appointment is not found. + * @throws AppointmentClashException If the appointment time conflicts with an existing appointment. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws UnloadedStorageException, PatientNotFoundException, + AppointmentClashException { + manager.addAppointment(appointment); + ui.showAppointmentAdded(manager.getAppointments()); + } + +} diff --git a/src/main/java/command/AddPatientCommand.java b/src/main/java/command/AddPatientCommand.java new file mode 100644 index 0000000000..2168effa2d --- /dev/null +++ b/src/main/java/command/AddPatientCommand.java @@ -0,0 +1,40 @@ +package command; + +import exception.DuplicatePatientIDException; +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import manager.Patient; +import miscellaneous.Ui; + +/** + * Represents a command to add a patient to the system. + */ +public class AddPatientCommand extends Command { + + protected Patient patient; + + /** + * Constructs an AddPatientCommand with the specified patient. + * + * @param patient The patient to be added. + */ + public AddPatientCommand(Patient patient) { + this.patient = patient; + } + + /** + * Executes the add patient command. + * Adds the patient to the management system and shows confirmation via UI. + * + * @param manager The management system to update. + * @param ui The user interface to interact with the user. + * @throws DuplicatePatientIDException If a patient with the same ID already exists. + * @throws UnloadedStorageException If the storage is not properly initialized. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) + throws DuplicatePatientIDException, UnloadedStorageException { + manager.addPatient(patient); + ui.showPatientAdded(manager.getPatients()); + } +} diff --git a/src/main/java/command/AddPrescriptionCommand.java b/src/main/java/command/AddPrescriptionCommand.java new file mode 100644 index 0000000000..4c1e0accaf --- /dev/null +++ b/src/main/java/command/AddPrescriptionCommand.java @@ -0,0 +1,62 @@ +package command; + +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import manager.Prescription; +import miscellaneous.Ui; + +/** + * Command to add a new prescription to the system. + * Handles the creation of a new prescription and displays + * feedback about the operation to the user. + */ +//@@author Basudeb2005 +public class AddPrescriptionCommand extends Command { + private final Prescription prescription; + + /** + * Constructs an AddPrescriptionCommand with the specified prescription. + * + * @param prescription The prescription to be added to the system + */ + public AddPrescriptionCommand(Prescription prescription) { + this.prescription = prescription; + } + + /** + * Executes the add prescription command. + * Adds the prescription to the management system and displays confirmation. + * Shows error message if the patient ID doesn't exist. + * + * @param manager The management system that handles the data + * @param ui The user interface to display results + * @throws UnloadedStorageException If there was an error saving to storage + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws UnloadedStorageException { + try { + Prescription addedPrescription = manager.addPrescription(prescription); + ui.showLine(); + System.out.println("Successfully added prescription:"); + System.out.println(addedPrescription.toString()); + System.out.println(""); + System.out.println("Prescription has been generated."); + System.out.println("View the prescription for the patient with ID: " + addedPrescription.getPatientId()); + System.out.println("and prescription ID: " + addedPrescription.getPrescriptionId()); + ui.showLine(); + } catch (IllegalArgumentException e) { + ui.showError("Failed to add prescription: " + e.getMessage()); + } + } + + /** + * Returns whether this command should exit the application. + * + * @return false (this command does not exit the application) + */ + @Override + public boolean isExit() { + return false; + } +} + diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java new file mode 100644 index 0000000000..f3f69db48f --- /dev/null +++ b/src/main/java/command/Command.java @@ -0,0 +1,44 @@ +package command; + +import exception.PatientNotFoundException; +import exception.AppointmentClashException; +import exception.DuplicatePatientIDException; +import exception.InvalidInputFormatException; +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import miscellaneous.Ui; + +/** + * Abstract class representing a command in the clinic management system. + * All commands must extend this class and implement the execute method. + * Follows the Command pattern to encapsulate requests as objects. + */ +public abstract class Command { + + /** + * Executes the command with the given management system and UI. + * Each subclass must implement this method with its specific behavior. + * + * @param manager The management system that provides operations on data + * @param ui The user interface to display results + * @throws DuplicatePatientIDException If a patient with the same ID already exists + * @throws UnloadedStorageException If there was an error with storage operations + * @throws PatientNotFoundException If a requested patient was not found + * @throws AppointmentClashException If an appointment conflicts with existing ones + * @throws InvalidInputFormatException If the input format is invalid + */ + public abstract void execute(ManagementSystem manager, Ui ui) + throws DuplicatePatientIDException, UnloadedStorageException, PatientNotFoundException, + AppointmentClashException, InvalidInputFormatException; + + /** + * Returns whether this command should exit the application. + * Default implementation returns false. + * Can be overridden by commands that should exit the application. + * + * @return true if the command should cause the application to exit, false otherwise + */ + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/command/DeleteAppointmentCommand.java b/src/main/java/command/DeleteAppointmentCommand.java new file mode 100644 index 0000000000..a81525ac22 --- /dev/null +++ b/src/main/java/command/DeleteAppointmentCommand.java @@ -0,0 +1,45 @@ +package command; + +import exception.UnloadedStorageException; +import manager.Appointment; +import manager.ManagementSystem; +import miscellaneous.Ui; + +/** + * Represents a command to delete an existing appointment from the management system. + */ +public class DeleteAppointmentCommand extends Command { + protected String apptId; + + /** + * Constructs a DeleteAppointmentCommand with the specified appointment ID. + * + * @param apptId The unique identifier of the appointment to be deleted. + */ + public DeleteAppointmentCommand(String apptId) { + this.apptId = apptId; + } + + /** + * Gets the appointment ID associated with this command. + * + * @return The ID of the appointment to be deleted. + */ + public String getApptId() { + return apptId; + } + + + /** + * Executes the command to delete the appointment from the management system. + * + * @param manager The management system that contains the appointment. + * @param ui The user interface for displaying messages. + * @throws UnloadedStorageException If the storage component is not properly loaded. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws UnloadedStorageException { + Appointment removedAppointment = manager.deleteAppointment(apptId); + ui.showAppointmentDeleted(manager.getAppointments(), removedAppointment, apptId); + } +} diff --git a/src/main/java/command/DeletePatientCommand.java b/src/main/java/command/DeletePatientCommand.java new file mode 100644 index 0000000000..7283e8c565 --- /dev/null +++ b/src/main/java/command/DeletePatientCommand.java @@ -0,0 +1,36 @@ +package command; + +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import manager.Patient; +import miscellaneous.Ui; + +/** + * Represents a command to delete a patient from the system. + */ +public class DeletePatientCommand extends Command { + protected String nric; + + /** + * Constructs a DeletePatientCommand with the given NRIC. + * + * @param nric The NRIC of the patient to delete. + */ + public DeletePatientCommand(String nric) { + this.nric = nric; + } + + /** + * Executes the delete patient command. + * Removes the patient with the specified NRIC from the system and shows confirmation via UI. + * + * @param manager The management system to update. + * @param ui The user interface to interact with the user. + * @throws UnloadedStorageException If storage is not properly initialized. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws UnloadedStorageException { + Patient removedPatient = manager.deletePatient(nric); + ui.showPatientDeleted(removedPatient, nric); + } +} diff --git a/src/main/java/command/EditPatientCommand.java b/src/main/java/command/EditPatientCommand.java new file mode 100644 index 0000000000..6554cc33fc --- /dev/null +++ b/src/main/java/command/EditPatientCommand.java @@ -0,0 +1,58 @@ +package command; + +import exception.InvalidInputFormatException; +import exception.PatientNotFoundException; +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import miscellaneous.Ui; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +public class EditPatientCommand extends Command { + + // details: [0]=nric, [1]=name, [2]=dob, [3]=gender, [4]=address, [5]=phone + private final String[] details; + + public EditPatientCommand(String[] details) { + this.details = details; + } + + /** + * Executes the edit-patient command by validating the new date of birth (if present), + * and then delegating to {@code manager.editPatient(...)} with the appropriate parameters. + * + * @param manager The ManagementSystem that manages patient data. + * @param ui The UI used to show error or success messages. + * @throws UnloadedStorageException If editing the patient data fails when saving to storage. + * @throws PatientNotFoundException If no patient with the specified NRIC is found. + * @throws InvalidInputFormatException If the provided date is invalid or if newDob is after the current date. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws UnloadedStorageException, + PatientNotFoundException, InvalidInputFormatException { + String nric = details[0]; + String name = details[1]; + String dob = details[2]; + String gender = details[3]; + String addr = details[4]; + String phone = details[5]; + + // If dob is specified, we parse it here to confirm validity before calling editPatient + if (dob != null && !dob.isBlank()) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + try { + LocalDate.parse(dob, formatter); + } catch (DateTimeParseException e) { + ui.showError("Invalid date format for Date of Birth! Please use yyyy-MM-dd (e.g., 1990-05-12)."); + return; + } + } + + manager.editPatient(nric, name, dob, gender, addr, phone); + Ui.showLine(); + System.out.println("Edit-patient command executed."); + Ui.showLine(); + } +} diff --git a/src/main/java/command/EditPatientHistoryCommand.java b/src/main/java/command/EditPatientHistoryCommand.java new file mode 100644 index 0000000000..e8155fefcc --- /dev/null +++ b/src/main/java/command/EditPatientHistoryCommand.java @@ -0,0 +1,48 @@ +package command; + +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import miscellaneous.Ui; + +/** + * Represents a command to edit existing medical history entries for a patient. + */ +public class EditPatientHistoryCommand extends Command { + + /** + * String array containing: + * [0] = Patient NRIC + * [1] = Old history text to be replaced + * [2] = New history text to replace with + */ + private final String[] details; + + /** + * Constructs an EditPatientHistoryCommand with the specified details. + * + * @param details Contains patient NRIC, old history entry, and new history entry. + */ + public EditPatientHistoryCommand(String[] details) { + this.details = details; + } + + /** + * Executes the command to edit the medical history of a specified patient. + * + * @param manager The ManagementSystem managing patient data and operations. + * @param ui The Ui used to display output to the user. + * @throws UnloadedStorageException if saving changes to storage fails. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws UnloadedStorageException { + String nric = details[0]; + String oldHistory = details[1]; + String newHistory = details[2]; + + manager.editPatientHistory(nric, oldHistory, newHistory); + + Ui.showLine(); + System.out.println("Edit-history command executed."); + Ui.showLine(); + } +} diff --git a/src/main/java/command/ExitCommand.java b/src/main/java/command/ExitCommand.java new file mode 100644 index 0000000000..1bc842b02b --- /dev/null +++ b/src/main/java/command/ExitCommand.java @@ -0,0 +1,32 @@ +package command; + +import manager.ManagementSystem; +import miscellaneous.Ui; + +/** + * Represents a command to exit the application. + * This command terminates the program after displaying a goodbye message. + */ +public class ExitCommand extends Command { + + /** + * Executes the exit command by displaying the goodbye message. + * + * @param manager The management system (unused in this command). + * @param ui The user interface used to display the goodbye message. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) { + ui.showBye(); + } + + /** + * Indicates that this command should terminate the application. + * + * @return Returns true to exit the application. + */ + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/command/FindAppointmentCommand.java b/src/main/java/command/FindAppointmentCommand.java new file mode 100644 index 0000000000..681bca638e --- /dev/null +++ b/src/main/java/command/FindAppointmentCommand.java @@ -0,0 +1,43 @@ +package command; + +import java.util.List; + +import manager.Appointment; +import manager.ManagementSystem; +import miscellaneous.Ui; + +/** + * Command to find appointments for a patient by their NRIC. + * Interacts with the {@link ManagementSystem} to retrieve + * all appointments associated with a specific NRIC and displays them through the {@link Ui}. + * + *

This command follows the Command pattern, which encapsulates the appointment search + * functionality to allow for consistent execution without directly manipulating the system's components. + *

+ */ +public class FindAppointmentCommand extends Command { + protected String nric; + + /** + * Constructs FindAppointmentCommand with the specified NRIC. + * + * @param nric the NRIC of the patient whose appointments are to be found + */ + public FindAppointmentCommand(String nric) { + this.nric = nric; + } + + /** + * Executes the command to find appointments for a patient with the given NRIC. + * It retrieves the list of appointments from ManagementSystem + * and displays the results using Ui. + * + * @param manager ManagementSystem responsible for handling appointment data + * @param ui Ui responsible for displaying the list of appointments + */ + @Override + public void execute(ManagementSystem manager, Ui ui) { + List foundAppointments = manager.findAppointmentsByNric(nric); + ui.showAppointmentsFound(foundAppointments, nric); + } +} diff --git a/src/main/java/command/HelpCommand.java b/src/main/java/command/HelpCommand.java new file mode 100644 index 0000000000..55fe86576e --- /dev/null +++ b/src/main/java/command/HelpCommand.java @@ -0,0 +1,20 @@ +package command; + +import manager.ManagementSystem; +import miscellaneous.Ui; + +/** + * Represents a command to display all available commands and their usage formats. + */ +public class HelpCommand extends Command { + /** + * Executes the help command by displaying the help message. + * + * @param manager The management system (unused in this command). + * @param ui The user interface used to display all available commands of the application. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) { + ui.showHelp(); + } +} diff --git a/src/main/java/command/ListAppointmentCommand.java b/src/main/java/command/ListAppointmentCommand.java new file mode 100644 index 0000000000..acffb8f40d --- /dev/null +++ b/src/main/java/command/ListAppointmentCommand.java @@ -0,0 +1,21 @@ +package command; + +import manager.ManagementSystem; +import miscellaneous.Ui; + +/** + * Represents a command to list all appointments in the system. + * This command displays all current appointments in the appointment list of the management system. + */ +public class ListAppointmentCommand extends Command { + /** + * Executes the list appointments command by displaying all appointments. + * + * @param manager The management system containing the appointment list. + * @param ui The user interface for displaying the appointment list. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) { + ui.showAppointmentList(manager.getAppointments()); + } +} diff --git a/src/main/java/command/ListPatientCommand.java b/src/main/java/command/ListPatientCommand.java new file mode 100644 index 0000000000..794d64dd33 --- /dev/null +++ b/src/main/java/command/ListPatientCommand.java @@ -0,0 +1,22 @@ +package command; + +import manager.ManagementSystem; +import miscellaneous.Ui; + +/** + * Represents a command to list all patients in the system. + */ +public class ListPatientCommand extends Command { + + /** + * Executes the list patient command. + * Displays all patients stored in the management system using the UI. + * + * @param manager The management system containing the patients. + * @param ui The user interface to interact with the user. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) { + ui.showPatientList(manager.getPatients()); + } +} diff --git a/src/main/java/command/MarkApppointmentCommand.java b/src/main/java/command/MarkApppointmentCommand.java new file mode 100644 index 0000000000..b3b9a6b0de --- /dev/null +++ b/src/main/java/command/MarkApppointmentCommand.java @@ -0,0 +1,42 @@ +package command; + +import exception.UnloadedStorageException; +import manager.Appointment; +import manager.ManagementSystem; +import miscellaneous.Ui; + +/** + * Represents a command to mark an appointment as done using its unique appointment ID. + * This command interacts with the ManagementSystem to update the appointment status + * and utilizes the Ui to provide feedback to the user. + * + *

This class is part of the Command pattern implementation in the system, + * using the mark operation for appointments in a reusable and modular way.

+ */ +public class MarkApppointmentCommand extends Command { + protected String apptId; + + /** + * Constructs a MarkApppointmentCommand with the specified appointment ID. + * + * @param apptId the ID of the appointment to be marked as completed + */ + public MarkApppointmentCommand(String apptId) { + this.apptId = apptId; + } + + /** + * Executes the command to mark the appointment with the given ID as completed. + * It updates the appointment's status through the ManagementSystem and + * displays the updated appointment list and confirmation via the Ui. + * + * @param manager the ManagementSystem responsible for managing appointments + * @param ui the Ui responsible for displaying results to the user + * @throws UnloadedStorageException if the storage system was not initialized when the command was executed + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws UnloadedStorageException { + Appointment appointment = manager.markAppointment(apptId); + ui.showAppointmentMarked(manager.getAppointments(), appointment, apptId); + } +} diff --git a/src/main/java/command/SortAppointmentCommand.java b/src/main/java/command/SortAppointmentCommand.java new file mode 100644 index 0000000000..35326cca96 --- /dev/null +++ b/src/main/java/command/SortAppointmentCommand.java @@ -0,0 +1,45 @@ +package command; + +import exception.DuplicatePatientIDException; +import exception.UnloadedStorageException; +import manager.Appointment; +import manager.ManagementSystem; +import miscellaneous.Ui; + +import java.util.List; + +/** + * Represents a command to sort appointments in the management system. + */ +public class SortAppointmentCommand extends Command { + protected String type; + + /** + * Constructs a SortAppointmentCommand with the specified sorting type. + * + * @param type The type of sorting to perform ("date" or other supported types). + */ + public SortAppointmentCommand(String type) { + this.type = type; + } + + /** + * Executes the command to sort appointments in the management system. + * + * @param manager The management system containing the appointments. + * @param ui The user interface for displaying the sorted appointments. + * @throws DuplicatePatientIDException If duplicate patient IDs are encountered. + * @throws UnloadedStorageException If the storage component is not properly loaded. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws DuplicatePatientIDException, + UnloadedStorageException { + if (type.equals("date")) { + List sortedApptByDateTime = manager.sortAppointmentsByDateTime(manager.getAppointments()); + ui.showAppointmentList(sortedApptByDateTime); + } else { + List sortedApptById = manager.sortAppointmentsById(manager.getAppointments()); + ui.showAppointmentList(sortedApptById); + } + } +} diff --git a/src/main/java/command/StoreMedHistoryCommand.java b/src/main/java/command/StoreMedHistoryCommand.java new file mode 100644 index 0000000000..84060f4e59 --- /dev/null +++ b/src/main/java/command/StoreMedHistoryCommand.java @@ -0,0 +1,47 @@ +package command; + +import exception.PatientNotFoundException; +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import miscellaneous.Ui; + + +/** + * Represents a command to store medical history for a specified patient. + */ +//@@author jyukuan +public class StoreMedHistoryCommand extends Command { + /** + * Patient's NRIC to which the medical history belongs. + */ + private String nric; + /** + * The medical history (or histories) to store, typically in comma-separated format. + */ + private String medHistory; + + /** + * Constructs a StoreMedHistoryCommand with the necessary details. + * The first element in the array is the patient's NRIC, the second is the medical history text. + * + * @param details String array containing [0] = NRIC, [1] = Medical history text. + */ + public StoreMedHistoryCommand(String[] details) { + this.nric = details[0]; + this.medHistory = details[1]; + } + + + /** + * Executes the command to store the given medical history for the specified patient. + * + * @param manager The ManagementSystem that manages patient and appointment data. + * @param ui The Ui used to display success or error messages. + * @throws UnloadedStorageException if saving to storage fails due to uninitialized storage. + * @throws PatientNotFoundException if the patient with the given NRIC is not found. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws UnloadedStorageException, PatientNotFoundException { + manager.storeMedicalHistory(nric, medHistory); + } +} diff --git a/src/main/java/command/UnmarkAppointmentCommand.java b/src/main/java/command/UnmarkAppointmentCommand.java new file mode 100644 index 0000000000..669513e5a5 --- /dev/null +++ b/src/main/java/command/UnmarkAppointmentCommand.java @@ -0,0 +1,42 @@ +package command; + +import exception.UnloadedStorageException; +import manager.Appointment; +import manager.ManagementSystem; +import miscellaneous.Ui; + +/** + * Represents a command to unmark an appointment, indicating it is no longer completed. + * This command interacts with the ManagementSystem to revert the appointment status + * and utilizes the Ui to provide feedback to the user. + * + *

It is part of the Command pattern implementation, encapsulating the logic + * for unmarking appointments in a modular and reusable way.

+ */ +public class UnmarkAppointmentCommand extends Command { + protected String apptId; + + /** + * Constructs an UnmarkAppointmentCommand with the specified appointment ID. + * + * @param apptId the ID of the appointment to be unmarked + */ + public UnmarkAppointmentCommand(String apptId) { + this.apptId = apptId; + } + + /** + * Executes command to unmark the appointment with the given ID. + * It updates appointment's status through the ManagementSystem and + * displays the updated list and confirmation via the Ui. + * + * @param manager the ManagementSystem responsible for managing appointments + * @param ui the Ui responsible for displaying results to the user + * @throws UnloadedStorageException if the storage system was not initialized when the command was executed + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws UnloadedStorageException { + Appointment appointment = manager.unmarkAppointment(apptId); + ui.showAppointmentUnmarked(manager.getAppointments(), appointment, apptId); + } +} diff --git a/src/main/java/command/ViewAllPrescriptionsCommand.java b/src/main/java/command/ViewAllPrescriptionsCommand.java new file mode 100644 index 0000000000..d4322b8614 --- /dev/null +++ b/src/main/java/command/ViewAllPrescriptionsCommand.java @@ -0,0 +1,89 @@ +package command; + +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import manager.Patient; +import manager.Prescription; +import miscellaneous.Ui; + +import java.util.List; + +/** + * Command to view all prescriptions for a specific patient. + * Lists all prescriptions with their basic details for the given patient ID. + */ +//@@author Basudeb2005 +public class ViewAllPrescriptionsCommand extends Command { + private final String patientId; + + /** + * Constructs a ViewAllPrescriptionsCommand with the specified patient ID. + * + * @param patientId The ID of the patient whose prescriptions will be displayed + */ + public ViewAllPrescriptionsCommand(String patientId) { + this.patientId = patientId; + } + + /** + * Executes the view all prescriptions command. + * Retrieves and displays all prescriptions associated with the specified patient. + * Shows an appropriate message if no prescriptions exist or if the patient is not found. + * + * @param manager The management system that handles the data + * @param ui The user interface to display results + * @throws UnloadedStorageException If there was an error with storage operations + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws UnloadedStorageException { + Patient patient = manager.viewPatient(patientId); + if (patient == null) { + ui.showError("Patient with ID " + patientId + " not found."); + return; + } + + List prescriptions = manager.getPrescriptionsForPatient(patientId); + + ui.showLine(); + if (prescriptions.isEmpty()) { + System.out.println("No prescriptions found for patient " + patient.getName() + " (" + patientId + ")."); + } else { + System.out.println("Prescriptions for patient " + patient.getName() + " (" + patientId + "):"); + System.out.println(""); + + for (Prescription prescription : prescriptions) { + System.out.println("Prescription ID: " + prescription.getPrescriptionId()); + System.out.println("Date: " + prescription.getTimestamp().format( + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))); + + System.out.println("Symptoms:"); + for (String symptom : prescription.getSymptoms()) { + System.out.println("- " + symptom); + } + + System.out.println("Medicines:"); + for (String medicine : prescription.getMedicines()) { + System.out.println("- " + medicine); + } + + System.out.println("Notes: " + prescription.getNotes()); + System.out.println(""); + } + + System.out.println("Total prescriptions: " + prescriptions.size()); + System.out.println("Use 'view-prescription PRESCRIPTION_ID' to view details and generate HTML."); + } + ui.showLine(); + } + + /** + * Returns whether this command should exit the application. + * + * @return false (this command does not exit the application) + */ + @Override + public boolean isExit() { + return false; + } +} + diff --git a/src/main/java/command/ViewMedHistoryCommand.java b/src/main/java/command/ViewMedHistoryCommand.java new file mode 100644 index 0000000000..8739915b79 --- /dev/null +++ b/src/main/java/command/ViewMedHistoryCommand.java @@ -0,0 +1,43 @@ +package command; + +import exception.PatientNotFoundException; +import manager.ManagementSystem; +import miscellaneous.Ui; + + +/** + * Represents a command to view medical history for a patient, + * either by NRIC or by name. + */ +public class ViewMedHistoryCommand extends Command { + protected String type; + protected String nameOrIc; + + /** + * Constructs a ViewMedHistoryCommand with the necessary details. + * details[0] = "ic" or "n" (search type), details[1] = the actual NRIC or name. + * + * @param details String array indicating search type and the patient's NRIC or name. + */ + public ViewMedHistoryCommand(String[] details) { + this.type = details[0]; + this.nameOrIc = details[1]; + } + + /** + * Executes the command to view a patient's medical history. + * If type == "ic", it searches by NRIC; otherwise, it searches by name. + * + * @param manager The ManagementSystem that manages patient data. + * @param ui The Ui used to display output to the user. + * @throws PatientNotFoundException if no matching patient is found when searching by NRIC. + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws PatientNotFoundException { + if (type.equals("ic")) { + manager.viewMedicalHistoryByNric(nameOrIc); + } else { + manager.viewMedicalHistoryByName(nameOrIc); + } + } +} diff --git a/src/main/java/command/ViewPatientCommand.java b/src/main/java/command/ViewPatientCommand.java new file mode 100644 index 0000000000..b203e996e8 --- /dev/null +++ b/src/main/java/command/ViewPatientCommand.java @@ -0,0 +1,51 @@ +package command; + +import manager.ManagementSystem; +import manager.Patient; +import miscellaneous.Ui; + +/** + * Command to view a patient in the system by their NRIC. + * This command interacts with the {@link ManagementSystem} to retrieve + * a patient and displays their details through the {@link Ui}. + * + *

This command is part of the Command pattern used to encapsulate + * the viewing functionality of a patient, allowing easy execution + * without directly manipulating system objects. + *

+ * + * @author dylancmznus + */ +public class ViewPatientCommand extends Command { + protected String nric; + + /** + * Constructs ViewPatientCommand with the specified NRIC. + * + * @param nric the NRIC of the patient to be viewed + */ + public ViewPatientCommand(String nric) { + this.nric = nric; + } + + /** + * Executes the command to view a patient with the given NRIC. + * Retrieves the patient from ManagementSystem and + * displays the details using Ui. + * + * @param manager the {@link ManagementSystem} responsible for handling patient data + * @param ui the {@link Ui} responsible for displaying the patient information + * @throws AssertionError if the manager or ui instances are null + * @throws AssertionError if no patient is found for the given NRIC + */ + @Override + public void execute(ManagementSystem manager, Ui ui) { + assert manager != null : "ManagementSystem instance can't be empty"; + assert ui != null : "Ui instance can't be empty"; + + Patient matchedPatient = manager.viewPatient(nric); + assert matchedPatient != null : "No patient found for NRIC: " + nric; + + ui.showPatientViewed(matchedPatient, nric); + } +} diff --git a/src/main/java/command/ViewPrescriptionCommand.java b/src/main/java/command/ViewPrescriptionCommand.java new file mode 100644 index 0000000000..2e790f8a37 --- /dev/null +++ b/src/main/java/command/ViewPrescriptionCommand.java @@ -0,0 +1,79 @@ +package command; + +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import manager.Patient; +import manager.Prescription; +import miscellaneous.Ui; +import storage.Storage; + +import java.io.File; + +/** + * Command to view a specific prescription and generate its HTML document. + * Displays detailed prescription information and creates a printable + * HTML version of the prescription. + */ +//@@author Basudeb2005 +public class ViewPrescriptionCommand extends Command { + private final String prescriptionId; + + /** + * Constructs a ViewPrescriptionCommand with the specified prescription ID. + * + * @param prescriptionId The ID of the prescription to view + */ + public ViewPrescriptionCommand(String prescriptionId) { + this.prescriptionId = prescriptionId; + } + + /** + * Executes the view prescription command. + * Retrieves the prescription with the specified ID and displays its details. + * Also generates an HTML file for printing the prescription. + * + * @param manager The management system that handles the data + * @param ui The user interface to display results + * @throws UnloadedStorageException If there was an error with storage operations + */ + @Override + public void execute(ManagementSystem manager, Ui ui) throws UnloadedStorageException { + Prescription prescription = manager.getPrescriptionById(prescriptionId); + if (prescription == null) { + ui.showError("Prescription with ID " + prescriptionId + " not found."); + return; + } + + Patient patient = manager.viewPatient(prescription.getPatientId()); + + ui.showLine(); + System.out.println("Prescription details:"); + System.out.println(prescription.toString()); + System.out.println(""); + + // Generate HTML file + Storage.savePrescriptionHtml(prescription, patient); + + String fileName = "prescription_" + prescription.getPatientId() + "_" + + prescription.getPrescriptionId().split("-")[1] + ".html"; + String filePath = new File("").getAbsolutePath() + + File.separator + "data" + + File.separator + "prescriptions" + + File.separator + fileName; + + System.out.println("Prescription HTML file generated at: " + filePath); + System.out.println("Open this file in a web browser to view and print the prescription."); + ui.showLine(); + } + + /** + * Returns whether this command should exit the application. + * + * @return false (this command does not exit the application) + */ + @Override + public boolean isExit() { + return false; + } +} + diff --git a/src/main/java/exception/AppointmentClashException.java b/src/main/java/exception/AppointmentClashException.java new file mode 100644 index 0000000000..01a0c4ef39 --- /dev/null +++ b/src/main/java/exception/AppointmentClashException.java @@ -0,0 +1,18 @@ +package exception; + +/** + * Represents an exception specific to appointment clash. + * This exception is thrown when attempting to schedule an appointment that conflicts with + * an existing appointment in the system. + */ +public class AppointmentClashException extends Exception { + + /** + * Constructs an AppointmentClashException with the specified detail message. + * + * @param message The detail message explaining the appointment conflict + */ + public AppointmentClashException(String message) { + super(message); + } +} diff --git a/src/main/java/exception/DuplicatePatientIDException.java b/src/main/java/exception/DuplicatePatientIDException.java new file mode 100644 index 0000000000..4c77b3e51f --- /dev/null +++ b/src/main/java/exception/DuplicatePatientIDException.java @@ -0,0 +1,18 @@ +package exception; + +/** + * Represents an exception specific to duplicate patient IDs. + * This exception is thrown when attempting to add a patient with an ID that already + * exists in the system. + */ +public class DuplicatePatientIDException extends Exception { + + /** + * Constructs a DuplicatePatientIDException with the specified detail message. + * + * @param message The detail message explaining the duplicate ID issue + */ + public DuplicatePatientIDException(String message) { + super(message); + } +} diff --git a/src/main/java/exception/InvalidInputFormatException.java b/src/main/java/exception/InvalidInputFormatException.java new file mode 100644 index 0000000000..e80dd8ab57 --- /dev/null +++ b/src/main/java/exception/InvalidInputFormatException.java @@ -0,0 +1,18 @@ +package exception; + +/** + * Represents an exception specific to the invalid user input format. + * This exception is thrown when the user input does not match the expected format + * for a command or parameter. + */ +public class InvalidInputFormatException extends Exception { + + /** + * Constructs an InvalidInputFormatException with the specified detail message. + * + * @param message The detail message explaining the correct input format + */ + public InvalidInputFormatException(String message) { + super(message); + } +} diff --git a/src/main/java/exception/PatientNotFoundException.java b/src/main/java/exception/PatientNotFoundException.java new file mode 100644 index 0000000000..90244c800d --- /dev/null +++ b/src/main/java/exception/PatientNotFoundException.java @@ -0,0 +1,16 @@ +package exception; + +/** + * Exception thrown when a patient with the specified identifier is not found in the system. + */ +public class PatientNotFoundException extends Exception { + + /** + * Constructs a new PatientNotFoundException with the specified detail message. + * + * @param message The detail message explaining the reason for the exception. + */ + public PatientNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/exception/UnknownCommandException.java b/src/main/java/exception/UnknownCommandException.java new file mode 100644 index 0000000000..b03f5c4ba9 --- /dev/null +++ b/src/main/java/exception/UnknownCommandException.java @@ -0,0 +1,19 @@ +package exception; + +/** + * Represents an exception specific to the unknown user command. + * This exception is thrown when the user enters a command that is not recognized + * by the application. + */ +public class UnknownCommandException extends Exception { + + /** + * Constructs an UnknownCommandException with the specified message. + * + * @param message The message indicating the command is invalid/unknown. + */ + public UnknownCommandException(String message) { + super(message); + } + +} diff --git a/src/main/java/exception/UnloadedStorageException.java b/src/main/java/exception/UnloadedStorageException.java new file mode 100644 index 0000000000..5cf0875aba --- /dev/null +++ b/src/main/java/exception/UnloadedStorageException.java @@ -0,0 +1,19 @@ +package exception; + +/** + * Represents an exception specific to storage access failure. + * This exception is thrown when the system fails to load or save data to/from storage, + * typically due to file system issues or corrupt data. + */ +public class UnloadedStorageException extends Exception { + + /** + * Constructs an UnloadedStorageException with the specified detail message. + * + * @param message The detail message explaining the storage failure + */ + public UnloadedStorageException(String message) { + super(message); + } + +} diff --git a/src/main/java/manager/Appointment.java b/src/main/java/manager/Appointment.java new file mode 100644 index 0000000000..31cea2865d --- /dev/null +++ b/src/main/java/manager/Appointment.java @@ -0,0 +1,151 @@ +package manager; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +/** + * Represents an appointment in the clinic management system. + * Contains information about the patient (NRIC), appointment date/time, + * description, and completion status. + */ +public class Appointment { + + public static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + public static final DateTimeFormatter OUTPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd h:mm a"); + + private static int runningId = 100; + private final String id; + private final String nric; + private final LocalDateTime dateTime; + private final String description; + private boolean isDone; + + /** + * Constructs a new Appointment with auto-generated ID. + * + * @param nric The NRIC of the patient for this appointment + * @param dateTime The date and time of the appointment + * @param description The description of the appointment + */ + public Appointment(String nric, LocalDateTime dateTime, String description) { + assert nric != null && !nric.isBlank() : "NRIC cannot be null or blank"; + assert dateTime != null : "DateTime cannot be null"; + assert description != null && !description.isBlank() : "Description cannot be null or blank"; + + this.id = "A" + runningId++; + this.nric = nric; + this.dateTime = dateTime; + this.description = description; + this.isDone = false; + } + + /** + * Constructs an Appointment with a specific ID (used for loading from storage). + * + * @param id The predefined ID for the appointment + * @param nric The NRIC of the patient + * @param dateTime The date and time of the appointment + * @param description The description of the appointment + */ + public Appointment(String id, String nric, LocalDateTime dateTime, String description) { + this.id = id; + this.nric = nric; + this.dateTime = dateTime; + this.description = description; + } + + public String getId() { + return id; + } + + /** + * Sets the running ID counter for new appointments. + * + * @param newId The new starting value for ID generation + */ + public static void setRunningId(int newId) { + runningId = newId; + } + + public String getNric() { + return nric; + } + + public LocalDate getDate() { + return dateTime.toLocalDate(); + } + + public LocalTime getTime() { + return dateTime.toLocalTime(); + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public static int getRunningId() { + return runningId; + } + + public String getDescription() { + return description; + } + + /** + * Checks the completion status of the appointment. + * + * @return true if the appointment is completed, false otherwise + */ + public boolean isDone() { + return isDone; + } + + public void markAsDone() { + this.isDone = true; + } + + public void unmarkAsDone() { + this.isDone = false; + } + + /** + * Sets the completion status of the appointment. + * + * @param mark true to mark as done, false to mark as not done + */ + public void setIsDone(boolean mark) { + this.isDone = mark; + } + + /** + * Gets the status icon of the appointment for display purposes. + * + * @return "X" if done, " " (space) if not done + */ + public String getStatusIcon() { + return (isDone ? "X" : " "); + } + + /** + * Returns the formatted string representation of the appointment + * + * @return A formatted string showing appointment ID, status, NRIC, date/time and description + */ + @Override + public String toString() { + return "[" + id + "]" + "[" + this.getStatusIcon() + "]" + " - " + + nric + " - " + dateTime.format(OUTPUT_FORMAT) + " - " + description; + } + + /** + * Returns the string representation of the appointment in a file-friendly format (for storage). + * + * @return Pipe-delimited string containing all appointment's information + */ + public String toFileFormat() { + return id.substring(1) + "|" + this.isDone + "|" + this.nric + "|" + + dateTime.format(OUTPUT_FORMAT) + "|" + this.description; + } +} diff --git a/src/main/java/manager/ManagementSystem.java b/src/main/java/manager/ManagementSystem.java new file mode 100644 index 0000000000..3a1b39b0ba --- /dev/null +++ b/src/main/java/manager/ManagementSystem.java @@ -0,0 +1,622 @@ +package manager; + +import exception.PatientNotFoundException; +import exception.AppointmentClashException; +import exception.DuplicatePatientIDException; +import exception.InvalidInputFormatException; +import exception.UnloadedStorageException; +import miscellaneous.Ui; +import storage.Storage; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * The ManagementSystem class handles all business logic for the clinic management system. + * It manages patients, appointments, and prescriptions, and coordinates with the storage system. + * This class serves as the central component that maintains the state of the application + * and provides operations to access and modify that state. + */ +public class ManagementSystem { + private final List appointments; + private final List patients; + private final List prescriptions; + + /** + * Constructs a ManagementSystem with patients and appointments but no prescriptions. + * + * @param loadedPatients The list of patients to initialize with + * @param loadedAppointments The list of appointments to initialize with + */ + public ManagementSystem(List loadedPatients, List loadedAppointments) { + assert loadedPatients != null : "Patient list cannot be null"; + assert loadedAppointments != null : "Appointment list cannot be null"; + appointments = loadedAppointments; + patients = loadedPatients; + prescriptions = new ArrayList<>(); + } + + /** + * Constructs a ManagementSystem with patients, appointments, and prescriptions. + * + * @param loadedPatients The list of patients to initialize with + * @param loadedAppointments The list of appointments to initialize with + * @param loadedPrescriptions The list of prescriptions to initialize with + */ + public ManagementSystem(List loadedPatients, List loadedAppointments, + List loadedPrescriptions) { + assert loadedPatients != null : "Patient list cannot be null"; + assert loadedAppointments != null : "Appointment list cannot be null"; + assert loadedPrescriptions != null : "Prescription list cannot be null"; + appointments = loadedAppointments; + patients = loadedPatients; + prescriptions = loadedPrescriptions; + } + + /** + * Gets the list of all patients in the system. + * + * @return List of all Patient objects + */ + public List getPatients() { + return patients; + } + + /** + * Updates the list of appointments in the system. + * Replaces the entire appointment list with the provided one. + * + * @param appointments The new list of appointments to set + */ + public void setAppointments(List appointments) { + this.appointments.clear(); + this.appointments.addAll(appointments); + } + + /** + * Gets the list of all appointments in the system. + * + * @return List of all Appointment objects + */ + public List getAppointments() { + return appointments; + } + + /** + * Adds a new patient to the system. + * Checks for duplicate NRICs before adding the patient. + * Saves the updated patient list to storage after successful addition. + * + * @param patient The patient to add + * @throws DuplicatePatientIDException If a patient with the same NRIC already exists + * @throws UnloadedStorageException If there was an error saving to storage + */ + public void addPatient(Patient patient) throws DuplicatePatientIDException, UnloadedStorageException { + assert patient != null : "Patient cannot be null"; + assert patients != null : "Patient list cannot be null"; + + for (Patient existingPatient : patients) { + assert existingPatient != null : "Existing patient in list cannot be null"; + if (existingPatient.getId().equals(patient.getId())) { + throw new DuplicatePatientIDException("Patient ID already exists!"); + } + } + patients.add(patient); + Storage.savePatients(patients); + } + + /** + * Deletes a patient from the system by NRIC. + * Also removes all appointments associated with the deleted patient. + * Saves the updated patient and appointment lists to storage after successful deletion. + * + * @param nric The NRIC of the patient to delete + * @return The deleted Patient object, or null if no patient was found with the given NRIC + * @throws UnloadedStorageException If there was an error saving to storage + */ + public Patient deletePatient(String nric) throws UnloadedStorageException { + assert nric != null && !nric.isBlank() : "NRIC must not be null or blank"; + assert patients != null : "Patient list cannot be null"; + + for (Patient patient : patients) { + if (patient.getId().equals(nric)) { + patients.remove(patient); + // delete all appointments associated with a patient to be deleted + appointments.removeIf(appointment -> appointment.getNric().equals(nric)); + Storage.savePatients(patients); + Storage.saveAppointments(appointments); + return patient; + } + } + return null; + } + + //@@author dylancmznus + + /** + * Retrieves the patient object that matches the specified NRIC. + * + *

The method iterates through the list of existing patients to find a match + * based on the NRIC. If a match is found, the corresponding Patient object is returned; + * otherwise, it returns null.

+ * + * @param nric The NRIC of the patient to be retrieved. + * @return The Patient object matching the given NRIC, or null if no match is found. + * @throws AssertionError if the input NRIC is null or blank. + */ + public Patient viewPatient(String nric) { + assert nric != null && !nric.isBlank() : "NRIC must not be null or blank"; + Patient matchedPatient = null; + for (Patient patient : patients) { + if (patient.getId().equals(nric)) { + matchedPatient = patient; + break; + } + } + return matchedPatient; + } + + + /** + * Edits an existing patient's information. + * Updates only the fields that are provided (non-null). + * Saves changes to storage after successful update. + * + * @param nric The NRIC of the patient to edit (cannot be changed) + * @param newName New name for the patient, or null to keep unchanged + * @param newDob New date of birth, or null to keep unchanged + * @param newGender New gender, or null to keep unchanged + * @param newAddress New address, or null to keep unchanged + * @param newPhone New phone number, or null to keep unchanged + * @throws UnloadedStorageException If there was an error saving to storage + * @throws PatientNotFoundException If no patient with the given NRIC exists + */ + //@@author jyukuan + public void editPatient(String nric, String newName, String newDob, String newGender, String newAddress, + String newPhone) throws UnloadedStorageException, PatientNotFoundException, + InvalidInputFormatException { + + assert nric != null && !nric.isBlank() : "NRIC must not be null or blank"; + assert patients != null : "Patient list cannot be null"; + + Patient patient = findPatientByNric(nric); + if (patient == null) { + throw new PatientNotFoundException("Patient with NRIC " + nric + " not found."); + } + if (newName != null && !newName.isBlank()) { + patient.setName(newName); + } + if (newDob != null && !newDob.isBlank()) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate parsedDob = LocalDate.parse(newDob, formatter); + if (parsedDob.isAfter(LocalDate.now())) { + throw new InvalidInputFormatException("Date of birth must be before today."); + } + patient.setDob(parsedDob); + } catch (DateTimeParseException e) { + throw new InvalidInputFormatException("Invalid date format. Use yyyy-MM-dd."); + } + } + + if (newGender != null && !newGender.isBlank()) { + patient.setGender(newGender); + } + if (newAddress != null && !newAddress.isBlank()) { + patient.setAddress(newAddress); + } + if (newPhone != null && !newPhone.isBlank()) { + patient.setContactInfo(newPhone); + } + Storage.savePatients(patients); + System.out.println("Patient with NRIC " + nric + " updated successfully."); + } + + /** + * Stores new medical history entries for a patient. + * If an entry already exists in the patient's history, it will not be duplicated. + * Saves the updated patient list to storage. + * + * @param nric The NRIC of the patient + * @param medHistory Comma-separated medical history entries to add + * @throws PatientNotFoundException If no patient with the given NRIC exists + * @throws UnloadedStorageException If there was an error saving to storage + */ + public void storeMedicalHistory(String nric, String medHistory) throws PatientNotFoundException, + UnloadedStorageException { + Patient existingPatient = findPatientByNric(nric); + + assert nric != null && !nric.isBlank() : "NRIC must not be null or blank"; + assert medHistory != null && !medHistory.isBlank() : "Medical history must not be null or blank"; + + + if (existingPatient == null) { + throw new PatientNotFoundException("Patient with NRIC not found. Patient's history can not be added"); + } else { + Ui.showLine(); + } + + String[] historyEntries = medHistory.split(",\\s*"); + for (String entry : historyEntries) { + if (!existingPatient.getMedicalHistory().contains(entry.trim())) { + existingPatient.getMedicalHistory().add(entry.trim()); + } + } + Storage.savePatients(patients); + System.out.println("Medical history added for patient with NRIC: " + nric + "."); + Ui.showLine(); + } + + + /** + * Views the medical history for a single patient identified by NRIC, + * and displays it to the user via the Ui component. + * + * @param nric The NRIC of the patient whose medical history should be displayed. + * @throws PatientNotFoundException if no patient with the specified NRIC is found. + */ + public void viewMedicalHistoryByNric(String nric) throws PatientNotFoundException { + Patient foundPatients = findPatientByNric(nric.trim()); + + if (foundPatients == null) { + throw new PatientNotFoundException("No patient/patients found with NRIC " + nric + "."); + } else { + Ui.showLine(); + Ui.showPatientHistory(foundPatients); + } + } + + /** + * Finds one or more patients by name, then displays each patient's medical history. + * If multiple patients share the same name, all their histories are shown. + * + * @param name The name of the patient(s) whose medical history should be displayed. + */ + public void viewMedicalHistoryByName(String name) { + List foundPatients = findPatientsByName(name.trim()); + + Ui.showLine(); + + if (foundPatients.isEmpty()) { + System.out.println("No patients found with name '" + name + "'."); + Ui.showLine(); + } else { + System.out.println("Found " + foundPatients.size() + " patient(s) with name '" + name + "'"); + for (Patient p : foundPatients) { + Ui.showPatientHistory(p); + } + } + } + + /** + * Edits a specific medical history entry for a patient. + * Replaces the old history text with new text if the old text is found. + * + * @param nric The patient's unique identifier + * @param oldHistory The existing history text to be replaced + * @param newHistory The new history text to replace it with + * @throws UnloadedStorageException If there was an error saving to storage + */ + public void editPatientHistory(String nric, String oldHistory, String newHistory) throws UnloadedStorageException { + + assert nric != null && !nric.isBlank() : "NRIC must not be null or blank"; + assert oldHistory != null && !oldHistory.isBlank() : "Old history must not be blank"; + assert newHistory != null && !newHistory.isBlank() : "New history must not be blank"; + + Patient patient = findPatientByNric(nric); + if (patient == null) { + System.out.println("Patient with NRIC " + nric + " not found."); + return; + } + List histories = patient.getMedicalHistory(); + boolean foundOld = false; + for (int i = 0; i < histories.size(); i++) { + if (histories.get(i).equalsIgnoreCase(oldHistory.trim())) { + histories.set(i, newHistory.trim()); + foundOld = true; + System.out.println("Replaced old history \"" + oldHistory + "\" with \"" + newHistory + "\"."); + break; + } + } + Storage.savePatients(patients); + if (!foundOld) { + System.out.println("Old history \"" + oldHistory + "\" not found for patient " + patient.getName()); + } + } + + + /** + * Finds a patient by their NRIC, ignoring case. + * + * @param nric The NRIC string to search for. + * @return The Patient object if found, or null if no matching patient is found. + */ + public Patient findPatientByNric(String nric) { + String object = nric.trim().toUpperCase(); + for (Patient p : patients) { + String patientId = p.getId().trim().toUpperCase(); + if (patientId.equals(object)) { + return p; + } + } + return null; + } + + /** + * Finds a list of patients who have a matching name (case-insensitive). + * + * @param name The name string to search for. + * @return A List of Patient objects that match the given name (could be empty if none found). + */ + private List findPatientsByName(String name) { + List result = new ArrayList<>(); + for (Patient p : patients) { + if (p.getName().trim().equalsIgnoreCase(name)) { + result.add(p); + } + } + return result; + } + + //@@author chwenyee + + /** + * Adds a new appointment to the system. + * Checks for appointment clashes (within 1 hour) and verifies the patient exists. + * Saves the updated appointment list to storage after successful addition. + * + * @param appointment The appointment to add + * @throws UnloadedStorageException If there was an error saving to storage + * @throws PatientNotFoundException If the patient associated with the appointment doesn't exist + * @throws AppointmentClashException If the appointment clashes with an existing appointment + */ + public void addAppointment(Appointment appointment) throws UnloadedStorageException, PatientNotFoundException, + AppointmentClashException { + assert appointment != null : "Appointment cannot be null"; + assert patients != null : "Patient list cannot be null"; + + // Check if there is any scheduled appointment in the list clashing with this newly-added one + for (Appointment appointmentInList : appointments) { + long timeDiff = Math.abs(Duration.between(appointmentInList.getDateTime(), + appointment.getDateTime()).toMinutes()); + if (timeDiff < 60) { + throw new AppointmentClashException("This appointment clashes with another scheduled within 1 hour."); + } + } + + Patient patient = findPatientByNric(appointment.getNric()); + if (patient == null) { + throw new PatientNotFoundException("Patient with NRIC: " + appointment.getNric() + " not found"); + } + + appointments.add(appointment); + patient.addAppointment(appointment); + Storage.saveAppointments(appointments); + } + + /** + * Deletes an appointment from the system by appointment ID. + * Also removes the appointment from the patient's appointment list. + * Saves the updated appointment list to storage after successful deletion. + * + * @param apptId The ID of the appointment to delete + * @return The deleted Appointment object, or null if no appointment was found with the given ID + * @throws UnloadedStorageException If there was an error saving to storage + */ + public Appointment deleteAppointment(String apptId) throws UnloadedStorageException { + assert apptId != null && !apptId.isBlank() : "Appointment ID cannot be null or blank"; + assert appointments != null : "Appointment list cannot be null"; + + for (Appointment appointment : appointments) { + if (appointment.getId().equalsIgnoreCase(apptId)) { + appointments.remove(appointment); + Patient patient = findPatientByNric(appointment.getNric()); + if (patient != null) { + patient.deleteAppointment(apptId); + Storage.saveAppointments(appointments); + } + return appointment; + } + } + return null; + } + + /** + * Sorts a list of appointments by date and time. + * Orders appointments chronologically from earliest to latest. + * + * @param appointments The list of appointments to sort + * @return The sorted list of appointments + */ + public List sortAppointmentsByDateTime(List appointments) { + appointments.sort(Comparator.comparing(Appointment::getDateTime)); + return appointments; + } + + /** + * Sorts a list of appointments by their ID. + * Orders appointments alphanumerically based on their appointment ID. + * + * @param appointments The list of appointments to sort + * @return The sorted list of appointments + */ + public List sortAppointmentsById(List appointments) { + appointments.sort(Comparator.comparing(Appointment::getId)); + return appointments; + } + + /** + * Marks an appointment as completed. + * Updates the appointment status and saves changes to storage. + * + * @param apptId The ID of the appointment to mark as done + * @return The updated appointment, or null if no appointment with the given ID was found + * @throws UnloadedStorageException If there was an error saving to storage + */ + public Appointment markAppointment(String apptId) throws UnloadedStorageException { + for (Appointment appointment : appointments) { + if (appointment.getId().equalsIgnoreCase(apptId)) { + appointment.markAsDone(); + Storage.saveAppointments(appointments); + return appointment; + } + } + return null; + } + + /** + * Unmarks a previously completed appointment. + * Updates the appointment status and saves changes to storage. + * + * @param apptId The ID of the appointment to unmark + * @return The updated appointment, or null if no appointment with the given ID was found + * @throws UnloadedStorageException If there was an error saving to storage + */ + public Appointment unmarkAppointment(String apptId) throws UnloadedStorageException { + for (Appointment appointment : appointments) { + if (appointment.getId().equalsIgnoreCase(apptId)) { + appointment.unmarkAsDone(); + Storage.saveAppointments(appointments); + return appointment; + } + } + return null; + } + + + /** + * Finds all appointments associated with the specified NRIC. + * + *

This method searches through the list of stored appointments and collects + * all appointments that match the given NRIC exactly.

+ * + * @param nric The NRIC used to search for matching appointments. + * @return A list of appointments that are associated with the provided NRIC. + */ + public List findAppointmentsByNric(String nric) { + List matchingAppointments = new ArrayList<>(); + for (Appointment appt : appointments) { + if (appt.getNric().equals(nric)) { + matchingAppointments.add(appt); + } + } + return matchingAppointments; + } + + //@@author Basudeb2005 + + /** + * Gets all prescriptions stored in the system. + * + * @return A list of all prescription records + */ + public List getPrescriptions() { + return prescriptions; + } + + //@@author Basudeb2005 + + /** + * Adds a new prescription to the system. + * Verifies the patient exists before adding the prescription. + * Generates a unique prescription ID based on patient ID and prescription count. + * Saves the updated prescription list to storage. + * + * @param prescription The prescription to add (without final ID) + * @return The newly created prescription with final ID + * @throws IllegalArgumentException If the patient doesn't exist or maximum prescriptions reached + * @throws UnloadedStorageException If there was an error saving to storage + */ + public Prescription addPrescription(Prescription prescription) + throws IllegalArgumentException, UnloadedStorageException { + assert prescription != null : "Prescription cannot be null"; + assert patients != null : "Patient list cannot be null"; + + Patient patient = findPatientByNric(prescription.getPatientId()); + if (patient == null) { + throw new IllegalArgumentException("Patient with NRIC: " + prescription.getPatientId() + " not found"); + } + + // Generate prescription ID with counter + int prescriptionCount = 1; + for (Prescription p : prescriptions) { + if (p.getPatientId().equals(prescription.getPatientId())) { + prescriptionCount++; + } + } + + // Check for maximum prescription limit + final int MAX_PRESCRIPTIONS_PER_PATIENT = 100; // Reasonable upper limit + if (prescriptionCount > MAX_PRESCRIPTIONS_PER_PATIENT) { + throw new IllegalArgumentException("Maximum number of prescriptions (" + + MAX_PRESCRIPTIONS_PER_PATIENT + + ") reached for patient: " + prescription.getPatientId()); + } + + String prescriptionId = prescription.getPatientId() + "-" + prescriptionCount; + + // Create a new prescription with updated ID + Prescription newPrescription = new Prescription( + prescription.getPatientId(), + prescriptionId, + prescription.getTimestamp(), + prescription.getSymptoms(), + prescription.getMedicines(), + prescription.getNotes() + ); + + prescriptions.add(newPrescription); + + try { + Storage.savePrescriptions(prescriptions); + } catch (UnloadedStorageException e) { + // Roll back the addition if saving fails + prescriptions.remove(newPrescription); + throw e; // Re-throw to notify the caller + } + + return newPrescription; + } + + //@@author Basudeb2005 + + /** + * Retrieves all prescriptions for a specific patient. + * Filters the complete prescription list to find those matching the provided patient ID. + * + * @param patientId The unique identifier of the patient + * @return A list of prescriptions for the specified patient (may be empty if none found) + */ + public List getPrescriptionsForPatient(String patientId) { + List patientPrescriptions = new ArrayList<>(); + for (Prescription prescription : prescriptions) { + if (prescription.getPatientId().equals(patientId)) { + patientPrescriptions.add(prescription); + } + } + return patientPrescriptions; + } + + //@@author Basudeb2005 + + /** + * Finds a specific prescription by its unique identifier. + * + * @param prescriptionId The unique ID of the prescription to find + * @return The prescription if found, or null if no matching prescription exists + */ + public Prescription getPrescriptionById(String prescriptionId) { + for (Prescription prescription : prescriptions) { + if (prescription.getPrescriptionId().equals(prescriptionId)) { + return prescription; + } + } + return null; + } + +} diff --git a/src/main/java/manager/Patient.java b/src/main/java/manager/Patient.java new file mode 100644 index 0000000000..d9d7628bff --- /dev/null +++ b/src/main/java/manager/Patient.java @@ -0,0 +1,360 @@ +package manager; + +import exception.InvalidInputFormatException; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Represents a patient in the clinic management system. + * Stores patient details such as NRIC, name, date of birth, contact info, gender, + * address, medical history, and appointments. + */ +public class Patient { + + private String id; + private String name; + private LocalDate dob; + private String contactInfo; + private String gender; + private String address; + private final List medicalHistory; + private final List appointments; + + /** + * Constructs a new Patient object with the given details. + * + * @param id The NRIC of the patient. + * @param name The name of the patient. + * @param dobStr The date of birth in yyyy-MM-dd format. + * @param gender The gender (M or F). + * @param address The address. + * @param contactInfo The 8-digit contact number. + * @param medicalHistory A list of past medical history. + * @throws InvalidInputFormatException if any input format is invalid. + */ + public Patient(String id, String name, String dobStr, String gender, String address, + String contactInfo, List medicalHistory) throws InvalidInputFormatException { + assert id != null && !id.isBlank() : "Patient ID cannot be null or blank"; + assert name != null && !name.isBlank() : "Patient name cannot be null or blank"; + assert dobStr != null : "Date of birth cannot be null"; + assert gender != null : "Gender cannot be null"; + assert address != null : "Address cannot be null"; + assert contactInfo != null : "Contact info cannot be null"; + assert medicalHistory != null : "Medical history list cannot be null"; + + this.id = parseValidIC(id); + this.name = name; + this.dob = parseAndValidateDob(dobStr); + this.gender = checkGender(gender); + this.address = address; + this.contactInfo = parseContactInfo(contactInfo); + this.medicalHistory = new ArrayList<>(medicalHistory); + this.appointments = new ArrayList<>(); + } + + /** + * Parses a pipe-delimited line into a Patient object. + * Accepts either 6 tokens (if medical history is empty) or 7 tokens. + * + * @param line The line to parse. + * @return A Patient object or null if the format is invalid. + * @throws InvalidInputFormatException if any of the fields are invalid. + */ + public static Patient parseLoadPatient(String line) throws InvalidInputFormatException { + String[] tokens = line.split("\\|"); + if (tokens.length < 6 || tokens.length > 7) { + return null; + } + + String id = tokens[0]; + String name = tokens[1]; + String dobStr = tokens[2]; + String gender = tokens[3]; + String address = tokens[4]; + String contact = tokens[5]; + List medHistory = new ArrayList<>(); + + if (tokens.length == 7 && !tokens[6].isBlank()) { + medHistory = Arrays.stream(tokens[6].split(",")) + .map(String::trim) + .collect(Collectors.toList()); + } + + return new Patient(id, name, dobStr, gender, address, contact, medHistory); + } + + /** + * @return the patient NRIC + */ + public String getId() { + return id; + } + + /** + * @return the patient name + */ + public String getName() { + return name; + } + + /** + * @return the patient's date of birth + */ + public LocalDate getDob() { + return dob; + } + + /** + * @return the patient's gender + */ + public String getGender() { + return gender; + } + + /** + * @return the patient's address + */ + public String getAddress() { + return address; + } + + /** + * @return the patient's contact number + */ + public String getContactInfo() { + return contactInfo; + } + + /** + * @return the patient's medical history list + */ + public List getMedicalHistory() { + return medicalHistory; + } + + public void setName(String name) { + this.name = name; + } + + public void setDob(LocalDate dob) { + this.dob = dob; + } + + public void setContactInfo(String contactInfo) { + this.contactInfo = contactInfo; + } + + public void setGender(String gender) { + this.gender = gender; + } + + public void setAddress(String address) { + this.address = address; + } + + /** + * @return the patient's list of appointments + */ + public List getAppointments() { + return appointments; + } + + /** + * Adds an appointment to the patient. + * + * @param appointment Appointment to be added. + */ + public void addAppointment(Appointment appointment) { + assert appointment != null : "Appointment cannot be null"; + assert appointment.getNric().equals(this.id) : "Appointment NRIC must match patient ID"; + appointments.add(appointment); + } + + /** + * Removes an appointment from the patient by appointment ID. + * + * @param apptId ID of the appointment to be removed. + */ + public void deleteAppointment(String apptId) { + assert apptId != null && !apptId.isBlank() : "Appointment ID cannot be null or blank"; + for (Appointment appt : appointments) { + if (appt.getId().equals(apptId)) { + appointments.remove(appt); + break; + } + } + } + + /** + * Returns a string representation of the patient for display. + * + * @return Formatted patient details and appointments. + */ + @Override + public String toString() { + String formattedMedicalHistory = medicalHistory.isEmpty() ? "None" : String.join(", ", medicalHistory); + + String result = String.format( + "Patient NRIC: %s\n" + + "Name: %s\n" + + "Date of Birth: %s\n" + + "Gender: %s\n" + + "Address: %s\n" + + "Contact: %s\n" + + "Medical History: %s", + id, name, dob.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), + gender, address, contactInfo, formattedMedicalHistory); + + if (appointments.isEmpty()) { + result += "\nAppointments: None"; + } else { + result += "\nAppointments:"; + for (Appointment appt : appointments) { + result += String.format( + "\n- [%s][%s]: %s (%s)", + appt.getId(), + appt.getStatusIcon(), + appt.getDateTime().format(Appointment.OUTPUT_FORMAT), + appt.getDescription()); + } + } + return result; + } + + /** + * Returns a formatted string for list display. + * + * @return Compact patient details for list view. + */ + public String toStringForListView() { + String result = String.format( + "Patient NRIC: %s\n " + + "Name: %s\n " + + "Date of Birth: %s\n " + + "Gender: %s\n " + + "Address: %s\n " + + "Contact: %s", + id, name, dob.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")), gender, address, contactInfo); + + if (medicalHistory.isEmpty()) { + result += "\n Medical History: None"; + } else { + result += "\n Medical History:"; + for (String h : medicalHistory) { + result += "\n - " + h; + } + } + + if (appointments.isEmpty()) { + result += "\n Appointments: None"; + } else { + result += "\n Appointments:"; + for (Appointment appt : appointments) { + result += String.format( + "\n - [%s][%s]: %s (%s)", + appt.getId(), + appt.getStatusIcon(), + appt.getDateTime().format(Appointment.OUTPUT_FORMAT), + appt.getDescription()); + } + } + return result; + } + + /** + * Returns a pipe-delimited string for file storage. + * + * @return File format string. + */ + public String toFileFormat() { + String history = String.join(", ", this.medicalHistory); + return this.id + "|" + this.name + "|" + dob.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + + "|" + this.gender + "|" + this.address + "|" + this.contactInfo + "|" + history; + } + + /** + * Validates and parses the NRIC format. + * + * @param ic The NRIC string to validate. + * @return A validated NRIC string. + * @throws InvalidInputFormatException If the NRIC format is invalid. + */ + private String parseValidIC(String ic) throws InvalidInputFormatException { + if (ic == null || ic.length() != 9) { + throw new InvalidInputFormatException("IC must be exactly 9 characters long."); + } + String prefix = ic.substring(0, 1).toUpperCase(); + String numberPart = ic.substring(1, 8); + String suffix = ic.substring(8).toUpperCase(); + + if (!prefix.matches("[STFGM]")) { + throw new InvalidInputFormatException("IC must start with S, T, F, G, or M."); + } + if (!numberPart.matches("\\d{7}")) { + throw new InvalidInputFormatException("IC must contain 7 digits after the prefix."); + } + if (!suffix.matches("[A-Z]")) { + throw new InvalidInputFormatException("IC must end with an uppercase letter."); + } + return ic; + } + + /** + * Validates the gender input. + * + * @param gender Gender string to validate. + * @return Validated gender. + * @throws InvalidInputFormatException If the gender is not M or F. + */ + private String checkGender(String gender) throws InvalidInputFormatException { + if (gender.equals("M") || gender.equals("F")) { + return gender; + } else { + throw new InvalidInputFormatException("The gender must be either M (male) or F (female)"); + } + } + + /** + * Parses and validates the date of birth string. + * + * @param dobStr Date of birth in yyyy-MM-dd format. + * @return Parsed LocalDate object. + * @throws InvalidInputFormatException If format is incorrect or date is in the future. + */ + private LocalDate parseAndValidateDob(String dobStr) throws InvalidInputFormatException { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + try { + LocalDate dob = LocalDate.parse(dobStr, formatter); + if (dob.isAfter(LocalDate.now())) { + throw new InvalidInputFormatException("Date of birth must be before the current date."); + } + return dob; + } catch (DateTimeParseException e) { + throw new InvalidInputFormatException("Invalid date format. Use yyyy-MM-dd."); + } + } + + /** + * Validates and parses the contact number. + * + * @param contactInfo Contact number string. + * @return Validated contact number. + * @throws InvalidInputFormatException If not an 8-digit number. + */ + private String parseContactInfo(String contactInfo) throws InvalidInputFormatException { + try { + if (contactInfo.length() != 8 || !contactInfo.matches("\\d+")) { + throw new InvalidInputFormatException("Contact number must be 8 digits."); + } + return contactInfo; + } catch (NullPointerException e) { + throw new InvalidInputFormatException("Contact number cannot be null."); + } + } +} diff --git a/src/main/java/manager/Prescription.java b/src/main/java/manager/Prescription.java new file mode 100644 index 0000000000..de2b54344c --- /dev/null +++ b/src/main/java/manager/Prescription.java @@ -0,0 +1,288 @@ +package manager; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Represents a medical prescription in the clinic management system. + * Contains information about the patient, symptoms, prescribed medicines, + * and additional notes provided by the doctor. + */ +//@@author Basudeb2005 +public class Prescription { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + private final String patientId; + private final LocalDateTime timestamp; + private final String prescriptionId; + private final List symptoms; + private final List medicines; + private final String notes; + + /** + * Constructs a new Prescription with the current timestamp. + * This constructor is used when creating a new prescription in the system. + * + * @param patientId The ID of the patient this prescription is for + * @param symptoms List of patient symptoms + * @param medicines List of prescribed medicines + * @param notes Additional instructions or notes for the patient + */ + public Prescription(String patientId, List symptoms, List medicines, String notes) { + this.patientId = patientId; + this.timestamp = LocalDateTime.now(); + this.prescriptionId = patientId + "-" + "1"; // Will be updated to handle numbering + this.symptoms = new ArrayList<>(symptoms); + this.medicines = new ArrayList<>(medicines); + this.notes = notes; + } + + /** + * Constructs a Prescription with specified prescriptionId and timestamp. + * This constructor is primarily used when loading prescriptions from storage. + * + * @param patientId The ID of the patient this prescription is for + * @param prescriptionId The unique identifier for this prescription + * @param timestamp The date and time when the prescription was created + * @param symptoms List of patient symptoms + * @param medicines List of prescribed medicines + * @param notes Additional instructions or notes for the patient + */ + public Prescription(String patientId, String prescriptionId, LocalDateTime timestamp, + List symptoms, List medicines, String notes) { + this.patientId = patientId; + this.timestamp = timestamp; + this.prescriptionId = prescriptionId; + this.symptoms = new ArrayList<>(symptoms); + this.medicines = new ArrayList<>(medicines); + this.notes = notes; + } + + /** + * Gets the patient ID associated with this prescription. + * + * @return The patient's unique identifier + */ + public String getPatientId() { + return patientId; + } + + /** + * Gets the unique identifier for this prescription. + * + * @return The prescription's ID in format "patientID-number" + */ + public String getPrescriptionId() { + return prescriptionId; + } + + /** + * Gets the timestamp when this prescription was created. + * + * @return LocalDateTime representing when the prescription was written + */ + public LocalDateTime getTimestamp() { + return timestamp; + } + + /** + * Gets the list of symptoms recorded for this prescription. + * + * @return List of symptom descriptions + */ + public List getSymptoms() { + return symptoms; + } + + /** + * Gets the list of medicines prescribed. + * + * @return List of medicine names and dosage instructions + */ + public List getMedicines() { + return medicines; + } + + /** + * Gets any additional notes or instructions for this prescription. + * + * @return Notes string or empty if no notes were provided + */ + public String getNotes() { + return notes; + } + + /** + * Returns a string representation of the prescription with formatted details. + * Includes prescription ID, timestamp, patient ID, and lists of symptoms and medicines. + * + * @return A formatted multi-line string representation of the prescription + */ + @Override + public String toString() { + StringBuilder symptomsStr = new StringBuilder(); + for (String symptom : symptoms) { + symptomsStr.append("- ").append(symptom).append("\n"); + } + + StringBuilder medicinesStr = new StringBuilder(); + for (String medicine : medicines) { + medicinesStr.append("- ").append(medicine).append("\n"); + } + + return String.format( + "Prescription [%s] (%s)\n" + + "Patient ID: %s\n" + + "Symptoms: \n%s" + + "Medicines: \n%s" + + "Notes: %s", + prescriptionId, timestamp.format(DATE_TIME_FORMATTER), + patientId, symptomsStr.toString(), medicinesStr.toString(), notes); + } + + /** + * Converts the prescription to a storage-friendly string format. + * Uses pipe-delimited format to store all prescription attributes. + * + * @return A string representation suitable for file storage + */ + public String toFileFormat() { + return String.join("|", + prescriptionId, + patientId, + timestamp.format(DATE_TIME_FORMATTER), + String.join(",", symptoms), + String.join(",", medicines), + notes); + } + + /** + * Creates a Prescription object from a storage file entry. + * Parses the pipe-delimited string from storage into a Prescription object. + * + * @param fileEntry The string from the storage file + * @return A new Prescription object with the stored data + */ + public static Prescription fromFileFormat(String fileEntry) { + String[] parts = fileEntry.split("\\|"); + + // Check if we have sufficient parts to create a valid prescription + if (parts.length < 5) { + System.out.println("Warning: Invalid prescription format: " + fileEntry); + return null; + } + + String prescriptionId = parts[0]; + String patientId = parts[1]; + LocalDateTime timestamp = LocalDateTime.parse(parts[2], DATE_TIME_FORMATTER); + List symptoms = parts[3].isEmpty() ? new ArrayList<>() : Arrays.asList(parts[3].split(",")); + List medicines = parts[4].isEmpty() ? new ArrayList<>() : Arrays.asList(parts[4].split(",")); + String notes = parts.length > 5 ? parts[5] : ""; // Default to empty string if notes aren't provided + + return new Prescription(patientId, prescriptionId, timestamp, symptoms, medicines, notes); + } + + /** + * Generates a formatted HTML document for the prescription. + * Creates a professional-looking prescription that can be printed. + * Includes patient details if provided, symptoms, medicines, and notes. + * + * @param patient The Patient object (can be null if patient details unavailable) + * @return A string containing HTML markup for the prescription + */ + public String generateHtml(Patient patient) { + StringBuilder html = new StringBuilder(); + html.append("\n") + .append("\n") + .append("\n") + .append(" \n") + .append(" \n") + .append(" Prescription ").append(prescriptionId).append("\n") + .append(" \n") + .append("\n") + .append("\n") + .append("
\n") + .append("
\n") + .append("

ClinicEase Medical Prescription

\n") + .append("

Prescription ID: ").append(prescriptionId).append("

\n") + .append("

Date: ").append(timestamp.format(DATE_TIME_FORMATTER)).append("

\n") + .append("
\n"); + + if (patient != null) { + html.append("
\n") + .append("

Patient Information

\n") + .append(" \n") + .append(" \n") + .append(" \n") + .append(" \n") + .append(" \n") + .append(" \n") + .append("
Patient ID:").append(patient.getId()).append("
Name:").append(patient.getName()).append("
Gender:").append(patient.getGender()).append("
Date of Birth:").append(patient.getDob()).append("
Contact:").append(patient.getContactInfo()).append("
\n") + .append("
\n"); + } else { + html.append("
\n") + .append("

Patient Information

\n") + .append(" \n") + .append(" \n") + .append("
Patient ID:").append(patientId).append("
\n") + .append("
\n"); + } + + html.append("
\n") + .append("

Symptoms

\n") + .append("
    \n"); + + for (String symptom : symptoms) { + html.append("
  • ").append(symptom).append("
  • \n"); + } + + html.append("
\n") + .append("
\n") + .append("
\n") + .append("

Prescribed Medications

\n") + .append("
    \n"); + + for (String medicine : medicines) { + html.append("
  • ").append(medicine).append("
  • \n"); + } + + html.append("
\n") + .append("
\n"); + + if (notes != null && !notes.isEmpty()) { + html.append("
\n") + .append("

Special Instructions

\n") + .append("

").append(notes).append("

\n") + .append("
\n"); + } + + html.append("
\n") + .append("

This prescription was generated by ClinicEase System

\n") + .append(" \n") + .append("
\n") + .append("
\n") + .append("\n") + .append("\n"); + + return html.toString(); + } +} + diff --git a/src/main/java/miscellaneous/Parser.java b/src/main/java/miscellaneous/Parser.java new file mode 100644 index 0000000000..43df5c2f44 --- /dev/null +++ b/src/main/java/miscellaneous/Parser.java @@ -0,0 +1,542 @@ +package miscellaneous; + + +import command.AddPatientCommand; +import command.Command; +import command.DeletePatientCommand; +import command.ExitCommand; +import command.AddAppointmentCommand; +import command.DeleteAppointmentCommand; +import command.EditPatientCommand; +import command.HelpCommand; +import command.ListAppointmentCommand; +import command.EditPatientHistoryCommand; +import command.ListPatientCommand; +import command.SortAppointmentCommand; +import command.StoreMedHistoryCommand; +import command.ViewPatientCommand; +import command.ViewMedHistoryCommand; +import command.MarkApppointmentCommand; +import command.UnmarkAppointmentCommand; +import command.FindAppointmentCommand; +import command.AddPrescriptionCommand; +import command.ViewAllPrescriptionsCommand; +import command.ViewPrescriptionCommand; +import exception.InvalidInputFormatException; +import exception.UnknownCommandException; +import manager.Appointment; +import manager.Patient; +import manager.Prescription; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static manager.Appointment.INPUT_FORMAT; + +/** + * Parses user input strings into executable Command objects. + * Handles all command types and parameter extraction for the clinic management system. + * Provides methods for parsing different types of commands including patient, appointment, + * medical history, and prescription management. + */ +public class Parser { + + /** + * Parses raw user input and returns the corresponding Command object. + * + * @param userInput The full command string entered by the user + * @return A Command object for execution + * @throws InvalidInputFormatException If user input format is invalid + * @throws UnknownCommandException If command is not recognized + */ + public static Command parse(String userInput) throws InvalidInputFormatException, UnknownCommandException { + // Handle empty input + if (userInput == null || userInput.trim().isEmpty()) { + throw new InvalidInputFormatException("Please enter a command."); + } + + // Split into two parts to extract the command keyword and its detail + String[] parts = userInput.split(" ", 2); + String commandWord = parts[0].toLowerCase(); + + switch (commandWord) { + case "bye": + return new ExitCommand(); + case "help": + return new HelpCommand(); + case "add-patient": + return new AddPatientCommand(parseAddPatient(userInput)); + case "delete-patient": + return new DeletePatientCommand(parseDeletePatient(userInput)); + case "view-patient": + return new ViewPatientCommand(parseViewPatient(userInput)); + case "list-patient": + return new ListPatientCommand(); + case "store-history": + return new StoreMedHistoryCommand(parseStoreHistory(userInput)); + case "view-history": + return new ViewMedHistoryCommand(parseViewHistory(userInput)); + case "add-appointment": + return new AddAppointmentCommand(parseAddAppointment(userInput)); + case "delete-appointment": + return new DeleteAppointmentCommand(parseDeleteAppointment(userInput)); + case "list-appointment": + return new ListAppointmentCommand(); + case "sort-appointment": + return new SortAppointmentCommand(parseSortAppointment(userInput)); + case "edit-patient": + return new EditPatientCommand(parseEditPatient(userInput)); + case "edit-history": + return new EditPatientHistoryCommand(parseEditHistory(userInput)); + case "mark-appointment": + return new MarkApppointmentCommand(parseMarkAppointment(userInput)); + case "unmark-appointment": + return new UnmarkAppointmentCommand(parseUnmarkAppointment(userInput)); + case "find-appointment": + return new FindAppointmentCommand(parseFindAppointment(userInput)); + case "add-prescription": + return new AddPrescriptionCommand(parseAddPrescription(userInput)); + case "view-all-prescriptions": + return new ViewAllPrescriptionsCommand(parseViewAllPrescriptions(userInput)); + case "view-prescription": + return new ViewPrescriptionCommand(parseViewPrescription(userInput)); + default: + throw new UnknownCommandException("Unknown command. Please try again."); + } + } + + private static Patient parseAddPatient(String input) throws InvalidInputFormatException { + String temp = input.replaceFirst("(?i)add-patient\\s*", ""); + String name = extractValue(temp, "n/"); + String nric = extractValue(temp, "ic/"); + String birthdate = extractValue(temp, "dob/"); + String gender = extractValue(temp, "g/"); + String phone = extractValue(temp, "p/"); + String address = extractValue(temp, "a/"); + String history = extractValue(temp, "h/"); + + if (name == null || nric == null || birthdate == null || gender == null || phone == null || address == null) { + throw new InvalidInputFormatException("Patient details are incomplete!" + System.lineSeparator() + + "Please use: add-patient n/NAME ic/NRIC dob/BIRTHDATE(yyyy-MM-dd) g/GENDER p/PHONE a/ADDRESS"); + } + + List medHistory = new ArrayList<>(); + if (history != null && !history.trim().isEmpty()) { + String[] entries = history.split(",\\s*"); + for (String entry : entries) { + medHistory.add(entry.trim()); + } + } + + return new Patient(nric.trim(), name.trim(), birthdate.trim(), + gender.trim(), address.trim(), phone.trim(), medHistory); + } + + private static String parseDeletePatient(String input) throws InvalidInputFormatException { + if (input.length() < 15) { + throw new InvalidInputFormatException("Invalid command format. Use: delete-patient NRIC"); + } + + String nric = input.substring(15).trim(); + return nric; + } + + private static String parseViewPatient(String input) throws InvalidInputFormatException { + if (input.length() < 13) { + throw new InvalidInputFormatException("Invalid command format. Use: view-patient NRIC"); + } + + String nric = input.substring(13).trim(); // Extract and trim NRIC + + if (nric.isEmpty() || !nric.matches("(?i)[A-Z]\\d{7}[A-Z]")) { + throw new InvalidInputFormatException("Invalid IC format. Please use a valid IC e.g. S1234567D"); + } + + return nric; + } + + public static String[] parseViewHistory(String input) throws InvalidInputFormatException { + // Remove the command prefix "view-history" (case-insensitive) and get the remaining string. + String temp = input.replaceFirst("(?i)view-history\\s*", ""); + String type; + String nameOrIc; + + // Check if the remaining string starts with "ic/" or "n/" (case-insensitive). + if (temp.toLowerCase().startsWith("ic/")) { + type = "ic"; + // Extract the real content after "ic/" using extractValue(...) + nameOrIc = extractValue(temp, "ic/"); + } else { + // If there's no explicit prefix, try to detect NRIC vs. name. + // Uses a simple regex matching a 9-character format: e.g., S1234567A + if (temp.matches("^[A-Za-z]\\d{7}[A-Za-z]$")) { + type = "ic"; + nameOrIc = temp.trim(); + } else { + // Otherwise, assume it's a name + type = "n"; + nameOrIc = temp.trim(); + } + } + + // Return null if the parsed value is null or empty + if (nameOrIc == null || nameOrIc.isEmpty()) { + throw new InvalidInputFormatException("Invalid format. Please use: view-history NRIC or view-history NAME"); + } + + // Return the result as [type, value] + return new String[]{type, nameOrIc}; + } + + public static String[] parseStoreHistory(String input) throws InvalidInputFormatException { + // Remove the command prefix "store-history" (case-insensitive) + // and get the remaining string. + String temp = input.replaceFirst("(?i)store-history\\s*", ""); + + // Extract n/NAME, ic/NRIC, and h/MEDICAL_HISTORY from the remaining string + String nric = extractValue(temp, "ic/"); + String medHistory = extractValue(temp, "h/"); + + // If any part is missing, return null to indicate a parse failure + if (nric == null || medHistory == null) { + throw new InvalidInputFormatException("Invalid format. " + + "Please use: store-history ic/NRIC h/MEDICAL_HISTORY"); + } + + // Return the trimmed values as an array + return new String[]{nric.trim(), medHistory.trim()}; + } + + private static Appointment parseAddAppointment(String input) throws InvalidInputFormatException { + String temp = input.replaceFirst("(?i)add-appointment\\s+", ""); + String nric = extractValue(temp, "ic/"); + String date = extractValue(temp, "dt/"); + String time = extractValue(temp, "t/"); + String desc = extractValue(temp, "dsc/"); + + if (nric == null || date == null || time == null || desc == null) { + String msg = "Missing details or wrong format for add-appointment!" + System.lineSeparator() + + "Please use: add-appointment ic/NRIC dt/DATE t/TIME dsc/DESCRIPTION"; + throw new InvalidInputFormatException(msg); + } + + // Check if the input format of Singapore's NRIC is valid + if (!nric.trim().matches("(?i)[STFGM]\\d{7}[A-Z]")) { + throw new InvalidInputFormatException("Invalid IC format. Please use a valid IC e.g. S1234567D"); + } + + try { + String combined = date.trim() + " " + time.trim(); + LocalDateTime dateTime = LocalDateTime.parse(combined, INPUT_FORMAT); + + LocalDateTime now = LocalDateTime.now(); + if (dateTime.isBefore(now)) { + throw new InvalidInputFormatException + ("The appointment date/time cannot be before the current date/time"); + } + + return new Appointment(nric.trim(), dateTime, desc.trim()); + } catch (DateTimeParseException e) { + throw new InvalidInputFormatException("Invalid date/time format. Please use: dt/yyyy-MM-dd and t/HHmm"); + } + } + + private static String parseDeleteAppointment(String input) throws InvalidInputFormatException { + if (!input.matches("(?i)delete-appointment\\s+A\\d+")) { + throw new InvalidInputFormatException("Invalid format! Please use: " + + "delete-appointment APPOINTMENT_ID"); + } + + String apptId = input.replaceFirst("(?i)delete-appointment\\s*", "").trim(); + return apptId; + } + + private static String parseSortAppointment(String input) throws InvalidInputFormatException { + String temp = input.replaceFirst("(?i)sort-appointment\\s*", ""); + + switch (temp.toLowerCase()) { + case "bydate": + return "date"; + case "byid": + return "id"; + default: + throw new InvalidInputFormatException("Invalid format! Please use: 'sort-appointment byDate' or " + + "'sort-appointment byId' (case-insensitive)."); + } + } + + private static String parseMarkAppointment(String input) throws InvalidInputFormatException { + String apptId = input.replaceFirst("(?i)mark-appointment\\s*", "").trim(); + if (apptId.isEmpty()) { + throw new InvalidInputFormatException("Invalid format! Use: mark-appointment APPOINTMENT_ID"); + } + return apptId; + } + + private static String parseUnmarkAppointment(String input) throws InvalidInputFormatException { + String apptId = input.replaceFirst("(?i)unmark-appointment\\s*", "").trim(); + if (apptId.isEmpty()) { + throw new InvalidInputFormatException("Invalid format! Use: unmark-appointment APPOINTMENT_ID"); + } + return apptId; + } + + private static String parseFindAppointment(String input) throws InvalidInputFormatException { + String patientId = input.replaceFirst("(?i)find-appointment\\s*", "").trim(); + if (patientId.isEmpty()) { + throw new InvalidInputFormatException("Invalid format! Use: find-appointment PATIENT_NRIC"); + } + return patientId; + } + + /** + * Extracts parameter values from command strings. + * + * @param input The string containing parameters + * @param prefix The parameter prefix to extract (e.g. "ic/") + * @return The extracted value or null if not found + */ + private static String extractValue(String input, String prefix) { + assert prefix != null : "Prefix cannot be null"; + + String lowerInput = input.toLowerCase(); + String lowerPrefix = prefix.toLowerCase(); + int start = -1; + + // Find the first occurrence of the prefix that is either at the start or come before blank space + // Ensure checks are not done at where the prefix can't fully fit + for (int i = 0; i <= lowerInput.length() - lowerPrefix.length(); i++) { + boolean isParamPrefixMatch = lowerInput.startsWith(lowerPrefix, i); + // Check if the character before the prefix is blank space in input to have a valid input format + boolean isParamAtValidPosition = (i == 0) || Character.isWhitespace(input.charAt(i - 1)); + if (isParamPrefixMatch && isParamAtValidPosition) { + start = i; + break; + } + } + + if (start < 0) { + return null; + } + + start += prefix.length(); + String[] possible = { + "n/", "ic/", "dob/", "g/", "p/", "a/", "dt/", "t/", + "dsc/", "h/", "old/", "new/", "s/", "m/", "nt/" + }; + int end = input.length(); + + // Determine where the current parameter's detail ends by finding the start of the next parameter + for (String p : possible) { + if (p.equalsIgnoreCase(prefix)) { + continue; + } + String lowerP = p.toLowerCase(); + // Find the next occurrence of p that is either at the start or come before blank space + for (int i = start; i <= lowerInput.length() - lowerP.length(); i++) { + boolean isNextParamPrefixMatch = lowerInput.startsWith(lowerP, i); + // Check if the character before the prefix is blank space in input to have a valid input format + boolean isNextParamAtValidPosition = (i == 0) + || Character.isWhitespace(input.charAt(i - 1)); + if (isNextParamPrefixMatch && isNextParamAtValidPosition) { + if (i < end) { + end = i; + } + break; + } + } + } + + String detail = input.substring(start, end).trim(); + return detail.isEmpty() ? null : detail; + } + + private static String[] parseEditPatient(String input) throws InvalidInputFormatException { + String temp = input.replaceFirst("(?i)edit-patient\\s*", ""); + String nric = extractValue(temp, "ic/"); + if (nric == null) { + throw new InvalidInputFormatException("Missing NRIC! Use: edit-patient ic/NRIC [n/NAME] " + + "[dob/BIRTHDATE] [g/GENDER] [a/ADDRESS] [p/PHONE]"); + } + String name = extractValue(temp, "n/"); + String dob = extractValue(temp, "dob/"); + String gender = extractValue(temp, "g/"); + String address = extractValue(temp, "a/"); + String phone = extractValue(temp, "p/"); + + return new String[]{nric, name, dob, gender, address, phone}; + } + + private static String[] parseEditHistory(String input) throws InvalidInputFormatException { + String temp = input.replaceFirst("(?i)edit-history\\s*", ""); + + String nric = extractValue(temp, "ic/"); + if (nric == null) { + throw new InvalidInputFormatException("Missing NRIC! Use: edit-history ic/NRIC " + + "old/OLD_HISTORY new/NEW_HISTORY"); + } + + String oldHistory = extractValue(temp, "old/"); + String newHistory = extractValue(temp, "new/"); + + if (oldHistory == null || newHistory == null) { + throw new InvalidInputFormatException("Missing old or new history text! Use: edit-history " + + "ic/NRIC old/OLD_TEXT new/NEW_TEXT"); + } + + return new String[]{nric, oldHistory, newHistory}; + } + + /** + * Parses patient data from storage format. + * + * @param line The pipe-delimited storage string of the patient + * @return Patient object or null if invalid + */ + public static Patient parseLoadPatient(String line) throws InvalidInputFormatException { + String[] tokens = line.split("\\|"); + boolean isHistoryNonpresent = tokens.length == 6; + if (tokens.length < 6) { + return null; + } + + String id = tokens[0]; + String name = tokens[1]; + String dobStr = tokens[2]; + String gender = tokens[3]; + String address = tokens[4]; + String contact = tokens[5]; + List medHistory = isHistoryNonpresent ? new ArrayList<>() : Arrays.stream(tokens[6].split(",")) + .map(String::trim) + .collect(Collectors.toList()); + + return new Patient(id, name, dobStr, gender, address, contact, medHistory); + } + + /** + * Parses appointment data from storage format. + * + * @param line The pipe-delimited storage string of the appointment + * @return Appointment object or null if invalid + */ + public static Appointment parseLoadAppointment(String line) { + if (line.startsWith("countId:")) { + return null; + } + + String[] tokens = line.split("\\|"); + if (tokens.length < 5) { + return null; + } + + try { + String id = tokens[0].trim(); + boolean isDone = tokens[1].equals("true"); + String nric = tokens[2].trim(); + String dateTimeStr = tokens[3].trim(); + String desc = tokens[4].trim(); + + LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, Appointment.OUTPUT_FORMAT); + Appointment appointment = new Appointment("A" + id, nric, dateTime, desc); + appointment.setIsDone(isDone); + + return appointment; + } catch (Exception e) { + return null; + } + } + + /** + * Parses input for the add-prescription command. + * Extracts patient ID, symptoms, medicines, and optional notes from the input string. + * Symptoms and medicines are converted from comma-separated strings to lists. + * + * @param input The raw user input string for add-prescription command + * @return A new Prescription object with the parsed data + * @throws InvalidInputFormatException If required parameters are missing or format is incorrect + */ + public static Prescription parseAddPrescription(String input) throws InvalidInputFormatException { + String temp = input.replaceFirst("(?i)add-prescription\\s*", ""); + + String patientId = extractValue(temp, "ic/"); + String symptoms = extractValue(temp, "s/"); + String medicines = extractValue(temp, "m/"); + String notes = extractValue(temp, "nt/"); + + if (patientId == null || symptoms == null || medicines == null) { + String msg = "Missing details or wrong format for add-prescription!" + System.lineSeparator() + + "Please use: add-prescription ic/PATIENT_ID s/SYMPTOMS m/MEDICINES [nt/NOTES]"; + throw new InvalidInputFormatException(msg); + } + + // Split symptoms by comma + List symptomsList = new ArrayList<>(); + if (symptoms != null && !symptoms.trim().isEmpty()) { + String[] entries = symptoms.split(",\\s*"); + for (String entry : entries) { + symptomsList.add(entry.trim()); + } + } + + // Split medicines by comma + List medicinesList = new ArrayList<>(); + if (medicines != null && !medicines.trim().isEmpty()) { + String[] entries = medicines.split(",\\s*"); + for (String entry : entries) { + medicinesList.add(entry.trim()); + } + } + + // Notes is optional, so it can be null + String finalNotes = (notes != null) ? notes.trim() : ""; + + return new Prescription(patientId.trim(), symptomsList, medicinesList, finalNotes); + } + + /** + * Parses input for the view-all-prescriptions command. + * Extracts patient ID from the command string. + * + * @param input The raw user input string for view-all-prescriptions command + * @return The patient ID whose prescriptions should be viewed + * @throws InvalidInputFormatException If the input format is incorrect or no patient ID is provided + */ + public static String parseViewAllPrescriptions(String input) throws InvalidInputFormatException { + String trimmedInput = input.trim(); + if (trimmedInput.equals("view-all-prescriptions") || trimmedInput.length() <= 22) { + throw new InvalidInputFormatException("Invalid command format. Use: view-all-prescriptions PATIENT_ID"); + } + + String patientId = trimmedInput.substring(22).trim(); + if (patientId.isEmpty()) { + throw new InvalidInputFormatException("Invalid command format. Use: view-all-prescriptions PATIENT_ID"); + } + return patientId; + } + + /** + * Parses input for the view-prescription command. + * Extracts prescription ID from the command string. + * + * @param input The raw user input string for view-prescription command + * @return The prescription ID to be viewed + * @throws InvalidInputFormatException If the input format is incorrect or no prescription ID is provided + */ + public static String parseViewPrescription(String input) throws InvalidInputFormatException { + String trimmedInput = input.trim(); + if (trimmedInput.equals("view-prescription") || trimmedInput.length() <= 17) { + throw new InvalidInputFormatException("Invalid command format. Use: view-prescription PRESCRIPTION_ID"); + } + + String prescriptionId = trimmedInput.substring(17).trim(); + if (prescriptionId.isEmpty()) { + throw new InvalidInputFormatException("Invalid command format. Use: view-prescription PRESCRIPTION_ID"); + } + return prescriptionId; + } + +} diff --git a/src/main/java/miscellaneous/Ui.java b/src/main/java/miscellaneous/Ui.java new file mode 100644 index 0000000000..ff5d8d991c --- /dev/null +++ b/src/main/java/miscellaneous/Ui.java @@ -0,0 +1,231 @@ +package miscellaneous; + +import manager.Appointment; +import manager.Patient; + + +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Scanner; + +/** + * The Ui class handles all user interaction for the clinic management system. + * It provides methods to display information to the user and collect input. + * This class is responsible for formatting output in a consistent way and + * providing a clean interface between the system and the user. + */ +public class Ui { + public static final String DIVIDER = "-".repeat(100); + public static final DateTimeFormatter OUTPUT_TIME_FORMAT = DateTimeFormatter.ofPattern("h:mm a"); + + private final Scanner sc; + + public Ui() { + sc = new Scanner(System.in); + } + + public static void showLine() { + System.out.println(DIVIDER); + } + + public void showWelcome() { + showLine(); + System.out.println("Welcome to ClinicEase!"); + System.out.println("Type a command, or 'bye' to exit."); + showLine(); + } + + public void showBye() { + showLine(); + System.out.println("Goodbye!"); + showLine(); + } + + public String readCommand() { + System.out.print("> "); + return sc.nextLine().trim(); + } + + public void showError(String message) { + showLine(); + System.out.println(message); + showLine(); + } + + public void showHelp() { + showLine(); + System.out.println("These are the available commands:"); + System.out.println("Patient: "); + System.out.println("- add-patient n/NAME ic/NRIC dob/BIRTHDATE g/GENDER p/PHONE a/ADDRESS"); + System.out.println("- delete-patient NRIC"); + System.out.println("- edit-patient ic/NRIC [n/NAME] [dob/BIRTHDATE] [g/GENDER] [a/ADDRESS] [p/PHONE]"); + System.out.println("- list-patient"); + System.out.println("- view-patient NRIC"); + System.out.println("- store-history n/NAME ic/NRIC h/MEDICAL_HISTORY"); + System.out.println("- view-history NRIC or view-history NAME"); + System.out.println("- edit-history ic/NRIC old/OLD_TEXT new/NEW_TEXT"); + System.out.println("Appointment: "); + System.out.println("- add-appointment ic/NRIC dt/DATE t/TIME dsc/DESCRIPTION"); + System.out.println("- delete-appointment APPOINTMENT_ID"); + System.out.println("- list-appointment"); + System.out.println("- mark-appointment APPOINTMENT_ID"); + System.out.println("- unmark-appointment APPOINTMENT_ID"); + System.out.println("- sort-appointment byDate or sort-appointment byId"); + System.out.println("- find-appointment PATIENT_NRIC"); + System.out.println("Prescription: "); + System.out.println("- add-prescription ic/PATIENT_ID s/SYMPTOMS m/MEDICINES [nt/NOTES]"); + System.out.println("- view-all-prescriptions PATIENT_ID"); + System.out.println("- view-prescription PRESCRIPTION_ID"); + showLine(); + } + + //@@author judHoka + public void showPatientAdded(List patients) { + showLine(); + System.out.println("Patient added successfully: " + patients.get(patients.size() - 1).getName()); + showLine(); + } + + public void showPatientDeleted(Patient removedPatient, String nric) { + if (removedPatient == null) { + showLine(); + System.out.println("Patient with NRIC " + nric + " not found."); + showLine(); + return; + } + showLine(); + System.out.println("Patient deleted successfully: " + removedPatient.getName()); + showLine(); + } + + //@@author dylancmznus + public void showPatientViewed(Patient matchedPatient, String nric) { + if (matchedPatient == null) { + showLine(); + System.out.println("Patient with NRIC " + nric + " not found."); + showLine(); + return; + } + System.out.println("-".repeat(42) + "Patient Details" + "-".repeat(42)); + System.out.println(matchedPatient); + showLine(); + } + + //@@author judHoka + public void showPatientList(List patients) { + if (patients.isEmpty()) { + showLine(); + System.out.println("No patients have been added."); + showLine(); + return; + } + + System.out.println("-".repeat(42) + "Patient Details" + "-".repeat(42)); + + int count = 1; + for (Patient p : patients) { + System.out.println(count + ". " + p.toStringForListView()); + showLine(); + count++; + } + } + + //@@author jyukuan + public static void showPatientHistory(Patient patient) { + System.out.println("Medical History for " + patient.getName() + " (NRIC: " + patient.getId() + "):"); + List histories = patient.getMedicalHistory(); + if (histories.isEmpty()) { + System.out.println("No medical history recorded."); + } else { + for (String h : histories) { + System.out.println("- " + h); + } + showLine(); + } + } + + //@@author chwenyee + public void showAppointmentAdded(List appointments) { + Appointment currentAppointment = appointments.get(appointments.size() - 1); + + showLine(); + System.out.println("Appointment added for NRIC: " + currentAppointment.getNric() + " on " + + currentAppointment.getDate() + " at " + currentAppointment.getTime().format(OUTPUT_TIME_FORMAT) + + "."); + System.out.println("Now you have " + appointments.size() + " appointment(s) in the list."); + showLine(); + } + + public void showAppointmentDeleted(List appointments, Appointment removedAppointment, String apptId) { + if (removedAppointment == null) { + showLine(); + System.out.println("No appointment found with ID: " + apptId + "."); + showLine(); + return; + } + + showLine(); + System.out.println("Appointment " + apptId + " is deleted successfully."); + System.out.println("Now you have " + appointments.size() + " appointment(s) in the list."); + showLine(); + } + + //@@author dylancmznus + public void showAppointmentMarked(List appointments, Appointment markedAppointment, String apptId) { + if (markedAppointment == null) { + showLine(); + System.out.println("No appointment found with ID: " + apptId + "."); + showLine(); + return; + } + + showLine(); + System.out.println("Appointment " + apptId + " is marked successfully."); + showLine(); + } + + public void showAppointmentUnmarked(List appointments, Appointment markedAppointment, String apptId) { + if (markedAppointment == null) { + showLine(); + System.out.println("No appointment found with ID: " + apptId + "."); + showLine(); + return; + } + + showLine(); + System.out.println("Appointment " + apptId + " is unmarked successfully."); + showLine(); + } + + //@@author Basudeb2005 + public void showAppointmentList(List appointments) { + if (appointments.isEmpty()) { + showLine(); + System.out.println("No appointments found."); + showLine(); + return; + } + + System.out.println("-".repeat(43) + "Appointments" + "-".repeat(45)); + int count = 1; + for (Appointment a : appointments) { + System.out.println(count + ". " + a); + count++; + } + showLine(); + } + + //@@author dylancmznus + public void showAppointmentsFound(List appointments, String nric) { + showLine(); + if (appointments.isEmpty()) { + System.out.println(" No appointments found for NRIC: " + nric); + } else { + System.out.println(" Appointments found for NRIC: " + nric); + for (Appointment appt : appointments) { + System.out.println(" - " + appt); + } + } + showLine(); + } +} diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/main/java/storage/Storage.java b/src/main/java/storage/Storage.java new file mode 100644 index 0000000000..6870a4ba7d --- /dev/null +++ b/src/main/java/storage/Storage.java @@ -0,0 +1,313 @@ +package storage; + +import exception.UnloadedStorageException; +import manager.Appointment; +import manager.Patient; +import manager.Prescription; +import miscellaneous.Parser; +import manager.ManagementSystem; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +/** + * Handles data persistence for the clinic management system. + * Provides methods for saving and loading patients, appointments, prescriptions, + * and generating HTML prescription documents. + */ +public class Storage { + private static String directoryPath; + private static String patientFilePath; + private static String appointmentFilePath; + private static String prescriptionFilePath; + private static String prescriptionDirPath; + private static Patient patients; + + /** + * Constructs a Storage object with specified directory. + * Initializes file paths for patients, appointments, and prescriptions. + * + * @param directory The root directory for storage files + */ + public Storage(String directory) { + directoryPath = directory; + patientFilePath = directory + File.separator + "patient_data.txt"; + appointmentFilePath = directory + File.separator + "appointment_data.txt"; + prescriptionFilePath = directory + File.separator + "prescription_data.txt"; + prescriptionDirPath = directory + File.separator + "prescriptions"; + } + + /** + * Saves patients to file storage. + * Writes each patient in the list to the patient file in a formatted string. + * + * @param patientList The list of patients to save + * @throws UnloadedStorageException If the storage was not properly initialized + * or if there was an error saving to file + */ + public static void savePatients(List patientList) throws UnloadedStorageException { + if (directoryPath == null || patientFilePath == null) { + throw new UnloadedStorageException("Storage not initialized with a directory!"); + } + + File dir = new File(directoryPath); + if (!dir.exists()) { + dir.mkdirs(); + } + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(patientFilePath))) { + for (Patient patient : patientList) { + writer.write(patient.toFileFormat()); + writer.newLine(); + } + } catch (IOException e) { + throw new UnloadedStorageException("Unable to save the patient!"); + } + } + + /** + * Loads patients from file storage. + * Reads and parses patient data from the patient file. + * + * @return A list of Patient objects loaded from file + * @throws UnloadedStorageException If there was an error reading from the file + * or if the data could not be properly parsed + */ + public static List loadPatients() throws UnloadedStorageException { + List patients = new ArrayList<>(); + File file = new File(patientFilePath); + if (!file.exists()) { + return patients; + } + + try (Scanner scanner = new Scanner(file)) { + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + Patient patient = Parser.parseLoadPatient(line); + if (patient != null) { + patients.add(patient); + } + } + } catch (Exception e) { + throw new UnloadedStorageException("Unable to load patient data!"); + } + + return patients; + } + + /** + * Saves appointments to file storage. + * Writes the running ID counter and each appointment in the list to the appointment file. + * + * @param appointmentList The list of appointments to save + * @throws UnloadedStorageException If the storage was not properly initialized + * or if there was an error saving to file + */ + public static void saveAppointments(List appointmentList) throws UnloadedStorageException { + if (directoryPath == null || appointmentFilePath == null) { + throw new UnloadedStorageException("Storage not initialized with a directory!"); + } + + File dir = new File(directoryPath); + if (!dir.exists()) { + dir.mkdirs(); + } + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(appointmentFilePath))) { + writer.write("countId:" + Appointment.getRunningId()); + writer.newLine(); + + for (Appointment appointment : appointmentList) { + writer.write(appointment.toFileFormat()); + writer.newLine(); + } + } catch (IOException e) { + throw new UnloadedStorageException("Unable to save the appointment!"); + } + } + + /** + * Loads appointments from file storage. + * Reads and parses appointment data from the appointment file. + * Also adds appointments to their respective patients. + * + * @param system The management system used to find patients for appointment association + * @return A list of Appointment objects loaded from file + * @throws UnloadedStorageException If there was an error reading from the file + * or if the data could not be properly parsed + */ + public static List loadAppointments(ManagementSystem system) throws UnloadedStorageException { + List appointments = new ArrayList<>(); + File file = new File(appointmentFilePath); + + if (!file.exists()) { + return appointments; + } + + try (Scanner scanner = new Scanner(file)) { + int countId = 100; + + if (scanner.hasNextLine()) { + String firstLine = scanner.nextLine(); + if (firstLine.startsWith("countId:")) { + String[] parts = firstLine.split(":"); + if (parts.length == 2) { + countId = Integer.parseInt(parts[1].trim()); + } + } + } + + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + if (!line.trim().isEmpty()) { + Appointment appointment = Parser.parseLoadAppointment(line); + if (appointment != null) { + appointments.add(appointment); + + Patient patient = system.findPatientByNric(appointment.getNric()); + if (patient != null) { + patient.addAppointment(appointment); + } + } + } + } + + Appointment.setRunningId(countId); + + } catch (Exception e) { + throw new UnloadedStorageException("Unable to load appointment data!"); + } + + return appointments; + } + + /** + * Saves prescriptions to file storage. + * Writes each prescription in the list to the prescription file in a formatted string. + * + * @param prescriptionList The list of prescriptions to save + * @throws UnloadedStorageException If the storage was not properly initialized + * or if there was an error saving to file + */ + public static void savePrescriptions(List prescriptionList) throws UnloadedStorageException { + if (directoryPath == null || prescriptionFilePath == null) { + throw new UnloadedStorageException("Storage not initialized with a directory!"); + } + + File dir = new File(directoryPath); + if (!dir.exists()) { + dir.mkdirs(); + } + + // Create a backup of the existing file if it exists + File file = new File(prescriptionFilePath); + if (file.exists()) { + File backupFile = new File(prescriptionFilePath + ".bak"); + file.renameTo(backupFile); + } + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(prescriptionFilePath))) { + int savedCount = 0; + for (Prescription prescription : prescriptionList) { + if (prescription != null) { + try { + String fileFormat = prescription.toFileFormat(); + writer.write(fileFormat); + writer.newLine(); + savedCount++; + } catch (Exception e) { + System.out.println("Warning: Unable to save prescription: " + e.getMessage()); + } + } + } + System.out.println("Saved " + savedCount + " prescriptions to storage."); + } catch (IOException e) { + throw new UnloadedStorageException("Unable to save prescriptions: " + e.getMessage()); + } + } + + /** + * Loads prescriptions from file storage. + * Reads and parses prescription data from the prescription file. + * + * @return A list of Prescription objects loaded from file + * @throws UnloadedStorageException If there was an error reading from the file + * or if the data could not be properly parsed + */ + public static List loadPrescriptions() throws UnloadedStorageException { + List prescriptions = new ArrayList<>(); + File file = new File(prescriptionFilePath); + + if (!file.exists()) { + return prescriptions; + } + + try (Scanner scanner = new Scanner(file)) { + int lineNumber = 0; + while (scanner.hasNextLine()) { + lineNumber++; + String line = scanner.nextLine(); + if (!line.trim().isEmpty()) { + try { + Prescription prescription = Prescription.fromFileFormat(line); + if (prescription != null) { + prescriptions.add(prescription); + } else { + System.out.println("Warning: Skipped invalid prescription at line " + lineNumber); + } + } catch (Exception e) { + // Log the error but continue processing other prescriptions + System.out.println("Warning: Error parsing prescription at line " + lineNumber + + ": " + e.getMessage()); + } + } + } + + // If no prescriptions were loaded but file exists and has content, this is suspicious + if (prescriptions.isEmpty() && lineNumber > 0) { + System.out.println("Warning: No valid prescriptions were loaded from file with " + + lineNumber + " lines"); + } + } catch (Exception e) { + throw new UnloadedStorageException("Unable to load prescription data: " + e.getMessage()); + } + + return prescriptions; + } + + /** + * Generates and saves an HTML representation of a prescription. + * Creates a formatted HTML file that can be viewed in a browser and printed. + * + * @param prescription The prescription to generate HTML for + * @param patient The patient associated with the prescription, may be null + * @throws UnloadedStorageException If the storage was not properly initialized + * or if there was an error writing the HTML file + */ + public static void savePrescriptionHtml(Prescription prescription, Patient patient) + throws UnloadedStorageException { + if (directoryPath == null || prescriptionDirPath == null) { + throw new UnloadedStorageException("Storage not initialized with a directory!"); + } + + File dir = new File(prescriptionDirPath); + if (!dir.exists()) { + dir.mkdirs(); + } + + String fileName = "prescription_" + prescription.getPatientId() + "_" + + prescription.getPrescriptionId().split("-")[1] + ".html"; + String filePath = prescriptionDirPath + File.separator + fileName; + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) { + writer.write(prescription.generateHtml(patient)); + } catch (IOException e) { + throw new UnloadedStorageException("Unable to generate HTML prescription: " + e.getMessage()); + } + } +} diff --git a/src/test/java/command/AddAppointmentCommandTest.java b/src/test/java/command/AddAppointmentCommandTest.java new file mode 100644 index 0000000000..616e7ae28b --- /dev/null +++ b/src/test/java/command/AddAppointmentCommandTest.java @@ -0,0 +1,56 @@ +package command; + +import exception.UnloadedStorageException; +import manager.Appointment; +import manager.ManagementSystem; +import manager.Patient; +import miscellaneous.Ui; +import storage.Storage; + +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AddAppointmentCommandTest { + private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + + @TempDir + Path tempDir; + private ManagementSystem manager; + private Ui ui; + private Storage storage; + + @BeforeEach + void setUp() throws UnloadedStorageException { + storage = new Storage(tempDir.toString()); + ui = new Ui(); + manager = new ManagementSystem(storage.loadPatients(), storage.loadAppointments(manager)); + } + + @Test + void execute_validAppointment_expectAppointmentAdded() throws Exception { + Patient patient = new Patient("S1234567D", "Billy", "1990-10-01", + "M", "124 High St", "81234567", new ArrayList<>()); + new AddPatientCommand(patient).execute(manager, ui); + + LocalDateTime dateTime = LocalDateTime.parse("2025-03-25 1900", DATE_TIME_FORMAT); + Appointment appointment = new Appointment("S1234567D", dateTime, "Checkup"); + new AddAppointmentCommand(appointment).execute(manager, ui); + + List appointments = manager.getAppointments(); + assertEquals(1, appointments.size()); + assertEquals(appointment, appointments.get(0)); + + Patient updatedPatient = manager.viewPatient("S1234567D"); + assertEquals(1, updatedPatient.getAppointments().size()); + assertEquals(appointment, updatedPatient.getAppointments().get(0)); + } +} diff --git a/src/test/java/command/AddPrescriptionCommandTest.java b/src/test/java/command/AddPrescriptionCommandTest.java new file mode 100644 index 0000000000..d91c479bd9 --- /dev/null +++ b/src/test/java/command/AddPrescriptionCommandTest.java @@ -0,0 +1,122 @@ +package command; + +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import manager.Patient; +import manager.Prescription; +import miscellaneous.Ui; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; +import storage.Storage; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +//@@author Basudeb2005 +public class AddPrescriptionCommandTest { + + private ManagementSystem system; + private Ui ui; + private ByteArrayOutputStream outputStream; + private PrintStream originalOut; + + @BeforeEach + public void setUp() { + // Save original System.out + originalOut = System.out; + + // Redirect System.out to capture output + outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + + try { + // Initialize Storage with temp directory + new Storage(System.getProperty("java.io.tmpdir") + File.separator + "clinicease_test"); + + // Initialize system and UI + system = new ManagementSystem(new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + ui = new Ui(); + + // Add a test patient + Patient patient = new Patient( + "S1234567A", "John Doe", "1990-01-01", "M", + "123 Main St", "98765432", new ArrayList<>() + ); + system.addPatient(patient); + } catch (Exception e) { + Assertions.fail("Failed to set up test: " + e.getMessage()); + } + } + + @Test + public void execute_validPrescription_success() { + // Create prescription and command + List symptoms = Arrays.asList("Fever", "Cough"); + List medicines = Arrays.asList("Paracetamol", "Cough syrup"); + Prescription prescription = new Prescription("S1234567A", symptoms, medicines, "Take after meals"); + AddPrescriptionCommand command = new AddPrescriptionCommand(prescription); + + try { + // Execute command + command.execute(system, ui); + + // Verify command output indicates success + String output = outputStream.toString(); + Assertions.assertTrue(output.contains("Successfully added prescription")); + Assertions.assertTrue(output.contains("S1234567A-1")); // Prescription ID + Assertions.assertTrue(output.contains("Fever")); + Assertions.assertTrue(output.contains("Paracetamol")); + Assertions.assertTrue(output.contains("Take after meals")); + + // Verify prescription was added to the system + Assertions.assertEquals(1, system.getPrescriptions().size()); + Assertions.assertNotNull(system.getPrescriptionById("S1234567A-1")); + } catch (UnloadedStorageException e) { + Assertions.fail("Should not throw exception for a valid prescription: " + e.getMessage()); + } + } + + @Test + public void execute_nonExistentPatient_showsError() { + // Create prescription for non-existent patient and command + List symptoms = Arrays.asList("Fever"); + List medicines = Arrays.asList("Paracetamol"); + Prescription prescription = new Prescription("NONEXISTENT", symptoms, medicines, ""); + AddPrescriptionCommand command = new AddPrescriptionCommand(prescription); + + try { + // Execute command + command.execute(system, ui); + + // Verify command output indicates error + String output = outputStream.toString(); + Assertions.assertTrue(output.contains("Failed to add prescription")); + Assertions.assertTrue(output.contains("Patient with NRIC: NONEXISTENT not found")); + + // Verify no prescription was added to the system + Assertions.assertEquals(0, system.getPrescriptions().size()); + } catch (UnloadedStorageException e) { + Assertions.fail("Should handle patient not found gracefully: " + e.getMessage()); + } + } + + @Test + public void isExit_returnsFalse() { + // Create command + List symptoms = Arrays.asList("Fever"); + List medicines = Arrays.asList("Paracetamol"); + Prescription prescription = new Prescription("S1234567A", symptoms, medicines, ""); + AddPrescriptionCommand command = new AddPrescriptionCommand(prescription); + + // Verify isExit returns false + Assertions.assertFalse(command.isExit()); + } +} + + + diff --git a/src/test/java/command/DeleteAppointmentCommandTest.java b/src/test/java/command/DeleteAppointmentCommandTest.java new file mode 100644 index 0000000000..0b6fc25d4a --- /dev/null +++ b/src/test/java/command/DeleteAppointmentCommandTest.java @@ -0,0 +1,56 @@ +package command; + +import exception.PatientNotFoundException; +import exception.AppointmentClashException; +import exception.DuplicatePatientIDException; +import exception.InvalidInputFormatException; +import exception.UnloadedStorageException; +import manager.Appointment; +import manager.ManagementSystem; +import manager.Patient; +import miscellaneous.Ui; +import storage.Storage; + +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class DeleteAppointmentCommandTest { + private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + + @TempDir + Path tempDir; + private ManagementSystem manager; + private Ui ui; + private Storage storage; + + @BeforeEach + void setUp() throws UnloadedStorageException { + storage = new Storage(tempDir.toString()); + ui = new Ui(); + manager = new ManagementSystem(storage.loadPatients(), storage.loadAppointments(manager)); + } + + @Test + void execute_validNric_expectAppointmentDeleted() throws DuplicatePatientIDException, UnloadedStorageException, + PatientNotFoundException, AppointmentClashException, InvalidInputFormatException { + Patient patient = new Patient("S1234567D", "Billy", "1990-10-01", + "M", "124 High St", "81234567", new ArrayList<>()); + new AddPatientCommand(patient).execute(manager, ui); + + LocalDateTime dateTime = LocalDateTime.parse("2025-03-25 1900", DATE_TIME_FORMAT); + Appointment appointment = new Appointment("S1234567D", dateTime, "Checkup"); + new AddAppointmentCommand(appointment).execute(manager, ui); + new DeleteAppointmentCommand(appointment.getId()).execute(manager, ui); + + assertEquals(0, manager.getAppointments().size(), "Size of appointment does not match"); + } +} diff --git a/src/test/java/command/ListAppointmentCommandTest.java b/src/test/java/command/ListAppointmentCommandTest.java new file mode 100644 index 0000000000..7ff8eef237 --- /dev/null +++ b/src/test/java/command/ListAppointmentCommandTest.java @@ -0,0 +1,76 @@ +package command; + +import exception.PatientNotFoundException; +import exception.AppointmentClashException; +import exception.DuplicatePatientIDException; +import exception.InvalidInputFormatException; +import exception.UnloadedStorageException; +import manager.Appointment; +import manager.ManagementSystem; +import manager.Patient; +import miscellaneous.Ui; +import storage.Storage; + +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class ListAppointmentCommandTest { + private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + + @TempDir + Path tempDir; + private ManagementSystem manager; + private Ui ui; + private Storage storage; + + @BeforeEach + void setUp() throws UnloadedStorageException { + storage = new Storage(tempDir.toString()); + ui = new Ui(); + manager = new ManagementSystem(storage.loadPatients(), storage.loadAppointments(manager)); + } + + @Test + void execute_always_requestsAppointmentsFromManager() throws DuplicatePatientIDException, UnloadedStorageException, + PatientNotFoundException, AppointmentClashException, InvalidInputFormatException { + LocalDateTime dateTime1 = LocalDateTime.parse("2025-03-25 1900", DATE_TIME_FORMAT); + LocalDateTime dateTime2 = LocalDateTime.parse("2025-03-28 2000", DATE_TIME_FORMAT); + LocalDateTime dateTime3 = LocalDateTime.parse("2025-03-23 1200", DATE_TIME_FORMAT); + + List patients = List.of( + new Patient("S1234567D", "Billy", "1990-10-01", + "M", "124 High St", "81234567", new ArrayList<>()), + new Patient("S2345678D", "James", "1980-12-31", + "M", "133 Main St", "81229312", new ArrayList<>()), + new Patient("S3456789D", "William", "1970-08-21", + "M", "17 Cornelia St", "81009214", new ArrayList<>()) + ); + manager.addPatient(patients.get(0)); + manager.addPatient(patients.get(1)); + manager.addPatient(patients.get(2)); + + List appointments = List.of( + new Appointment("S1234567D", dateTime1, "Checkup"), + new Appointment("S2345678D", dateTime2, "CT scan"), + new Appointment("S3456789D", dateTime3, "Consultation") + ); + manager.addAppointment(appointments.get(0)); + manager.addAppointment(appointments.get(1)); + manager.addAppointment(appointments.get(2)); + + new ListAppointmentCommand().execute(manager, ui); + + assertEquals(3, manager.getAppointments().size(), "Size of appointment does not match"); + } + +} diff --git a/src/test/java/command/SortAppointmentCommandTest.java b/src/test/java/command/SortAppointmentCommandTest.java new file mode 100644 index 0000000000..f73e9457f1 --- /dev/null +++ b/src/test/java/command/SortAppointmentCommandTest.java @@ -0,0 +1,103 @@ +package command; + +import exception.PatientNotFoundException; +import exception.AppointmentClashException; +import exception.DuplicatePatientIDException; +import exception.InvalidInputFormatException; +import exception.UnloadedStorageException; +import manager.Appointment; +import manager.ManagementSystem; +import manager.Patient; +import miscellaneous.Ui; +import storage.Storage; + +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +class SortAppointmentCommandTest { + private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + + @TempDir + Path tempDir; + private ManagementSystem manager; + private Ui ui; + private Storage storage; + + @BeforeEach + void setUp() throws UnloadedStorageException, DuplicatePatientIDException, PatientNotFoundException, + AppointmentClashException, InvalidInputFormatException { + storage = new Storage(tempDir.toString()); + ui = new Ui(); + manager = new ManagementSystem(storage.loadPatients(), storage.loadAppointments(manager)); + + List patients = List.of( + new Patient("S1234567D", "Billy", "1990-10-01", + "M", "124 High St", "81234567", new ArrayList<>()), + new Patient("S2345678D", "James", "1980-12-31", + "M", "133 Main St", "81229312", new ArrayList<>()), + new Patient("S3456789D", "William", "1970-08-20", + "M", "17 Cornelia St", "81009214", new ArrayList<>()) + ); + manager.addPatient(patients.get(0)); + manager.addPatient(patients.get(1)); + manager.addPatient(patients.get(2)); + + LocalDateTime dateTime1 = LocalDateTime.parse("2025-03-25 1900", DATE_TIME_FORMAT); + LocalDateTime dateTime2 = LocalDateTime.parse("2025-03-28 2000", DATE_TIME_FORMAT); + LocalDateTime dateTime3 = LocalDateTime.parse("2025-03-23 1200", DATE_TIME_FORMAT); + + List appointments = List.of( + new Appointment("S1234567D", dateTime1, "Checkup"), + new Appointment("S2345678D", dateTime2, "CT scan"), + new Appointment("S3456789D", dateTime3, "Consultation") + ); + manager.addAppointment(appointments.get(0)); + manager.addAppointment(appointments.get(1)); + manager.addAppointment(appointments.get(2)); + } + + @Test + void execute_appointmentsWithDifferentDateTime_sortsByDateInAscendingOrder() throws DuplicatePatientIDException, + UnloadedStorageException { + + new SortAppointmentCommand("date").execute(manager, ui); + List sortedAppointments = manager.getAppointments(); + + assertEquals(3, sortedAppointments.size(), "Size of appointment does not match"); + assertEquals("Consultation", sortedAppointments.get(0).getDescription()); + assertEquals("Checkup", sortedAppointments.get(1).getDescription()); + assertEquals("CT scan", sortedAppointments.get(2).getDescription()); + } + + @Test + void execute_appointmentsFirstSortedByDateThenId_sortsByIdInAscendingOrder() throws DuplicatePatientIDException, + UnloadedStorageException { + new SortAppointmentCommand("date").execute(manager, ui); + new SortAppointmentCommand("id").execute(manager, ui); + List sortedAppointments = manager.getAppointments(); + + assertEquals(3, sortedAppointments.size(), "Size of appointment does not match"); + assertEquals("Checkup", sortedAppointments.get(0).getDescription()); + assertEquals("CT scan", sortedAppointments.get(1).getDescription()); + assertEquals("Consultation", sortedAppointments.get(2).getDescription()); + } + + @Test + void execute_emptyList_expectEmptyList() throws DuplicatePatientIDException, UnloadedStorageException { + ManagementSystem emptyManager = new ManagementSystem(new ArrayList<>(), new ArrayList<>()); + + new SortAppointmentCommand("date").execute(emptyManager, ui); + assertTrue(emptyManager.getAppointments().isEmpty(), "Appointment list should be empty"); + } +} diff --git a/src/test/java/command/ViewAllPrescriptionsCommandTest.java b/src/test/java/command/ViewAllPrescriptionsCommandTest.java new file mode 100644 index 0000000000..376099dde5 --- /dev/null +++ b/src/test/java/command/ViewAllPrescriptionsCommandTest.java @@ -0,0 +1,138 @@ +package command; + +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import manager.Patient; +import manager.Prescription; +import miscellaneous.Ui; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; +import storage.Storage; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +//@@author Basudeb2005 +public class ViewAllPrescriptionsCommandTest { + + private ManagementSystem system; + private Ui ui; + private ByteArrayOutputStream outputStream; + private Patient testPatient; + private PrintStream originalOut; + + @BeforeEach + public void setUp() { + // Save original System.out + originalOut = System.out; + + // Redirect System.out to capture output + outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + + try { + // Initialize Storage with temp directory + new Storage(System.getProperty("java.io.tmpdir") + File.separator + "clinicease_test"); + + // Initialize system and UI + system = new ManagementSystem(new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + ui = new Ui(); + + // Add a test patient + testPatient = new Patient( + "S1234567A", "John Doe", "1990-01-01", "M", + "123 Main St", "98765432", new ArrayList<>() + ); + system.addPatient(testPatient); + } catch (Exception e) { + Assertions.fail("Failed to set up test: " + e.getMessage()); + } + } + + @Test + public void execute_patientWithPrescriptions_success() { + try { + // Add some prescriptions for the test patient + List symptoms1 = Arrays.asList("Fever", "Cough"); + List medicines1 = Arrays.asList("Paracetamol", "Cough syrup"); + system.addPrescription(new Prescription(testPatient.getId(), symptoms1, medicines1, "Take after meals")); + + List symptoms2 = Arrays.asList("Headache"); + List medicines2 = Arrays.asList("Ibuprofen"); + system.addPrescription(new Prescription(testPatient.getId(), symptoms2, medicines2, "Take with water")); + + // Create command + ViewAllPrescriptionsCommand command = new ViewAllPrescriptionsCommand(testPatient.getId()); + + // Execute command + command.execute(system, ui); + + // Verify command output + String output = outputStream.toString(); + Assertions.assertTrue(output.contains("Prescriptions for patient John Doe")); + Assertions.assertTrue(output.contains("Fever")); + Assertions.assertTrue(output.contains("Cough")); + Assertions.assertTrue(output.contains("Headache")); + Assertions.assertTrue(output.contains("Paracetamol")); + Assertions.assertTrue(output.contains("Ibuprofen")); + Assertions.assertTrue(output.contains("Take after meals")); + Assertions.assertTrue(output.contains("Take with water")); + Assertions.assertTrue(output.contains("Total prescriptions: 2")); + } catch (UnloadedStorageException e) { + Assertions.fail("Should not throw exception: " + e.getMessage()); + } + } + + @Test + public void execute_patientWithNoPrescriptions_showsMessage() { + try { + // Create command for patient with no prescriptions + ViewAllPrescriptionsCommand command = new ViewAllPrescriptionsCommand(testPatient.getId()); + + // Execute command + command.execute(system, ui); + + // Verify command output + String output = outputStream.toString(); + Assertions.assertTrue(output.contains("No prescriptions found for patient John Doe")); + } catch (UnloadedStorageException e) { + Assertions.fail("Should not throw exception for patient with no prescriptions: " + e.getMessage()); + } + } + + @Test + public void execute_nonExistentPatient_showsError() { + // Create command for non-existent patient + ViewAllPrescriptionsCommand command = new ViewAllPrescriptionsCommand("NONEXISTENT"); + + try { + // Execute command + command.execute(system, ui); + + // Verify command output indicates error + String output = outputStream.toString(); + Assertions.assertTrue(output.contains("Patient with ID NONEXISTENT not found")); + } catch (UnloadedStorageException e) { + Assertions.fail("Should handle patient not found gracefully: " + e.getMessage()); + } + } + + @Test + public void isExit_returnsFalse() { + // Create command + ViewAllPrescriptionsCommand command = new ViewAllPrescriptionsCommand("S1234567A"); + + // Verify isExit returns false + Assertions.assertFalse(command.isExit()); + } +} + + + + + diff --git a/src/test/java/command/ViewPrescriptionCommandTest.java b/src/test/java/command/ViewPrescriptionCommandTest.java new file mode 100644 index 0000000000..8be31acc59 --- /dev/null +++ b/src/test/java/command/ViewPrescriptionCommandTest.java @@ -0,0 +1,113 @@ +package command; + +import exception.UnloadedStorageException; +import manager.ManagementSystem; +import manager.Patient; +import manager.Prescription; +import miscellaneous.Ui; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; +import storage.Storage; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +//@@author Basudeb2005 +public class ViewPrescriptionCommandTest { + + private ManagementSystem system; + private Ui ui; + private ByteArrayOutputStream outputStream; + private Prescription testPrescription; + private PrintStream originalOut; + + @BeforeEach + public void setUp() { + // Save original System.out + originalOut = System.out; + + // Redirect System.out to capture output + outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + + try { + // Initialize Storage with temp directory + new Storage(System.getProperty("java.io.tmpdir") + File.separator + "clinicease_test"); + + // Initialize system and UI + system = new ManagementSystem(new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + ui = new Ui(); + + // Add a test patient + Patient patient = new Patient( + "S1234567A", "John Doe", "1990-10-01", "M", + "123 Main St", "98765432", new ArrayList<>() + ); + system.addPatient(patient); + + // Add a test prescription + List symptoms = Arrays.asList("Fever", "Cough"); + List medicines = Arrays.asList("Paracetamol", "Cough syrup"); + Prescription prescription = new Prescription("S1234567A", symptoms, medicines, "Take after meals"); + testPrescription = system.addPrescription(prescription); + } catch (Exception e) { + Assertions.fail("Failed to set up test: " + e.getMessage()); + } + } + + @Test + public void execute_existingPrescription_success() { + // Create command for existing prescription + ViewPrescriptionCommand command = new ViewPrescriptionCommand(testPrescription.getPrescriptionId()); + + try { + // Execute command + command.execute(system, ui); + + // Verify command output + String output = outputStream.toString(); + Assertions.assertTrue(output.contains("Prescription details")); + Assertions.assertTrue(output.contains(testPrescription.getPrescriptionId())); + Assertions.assertTrue(output.contains("Fever")); + Assertions.assertTrue(output.contains("Paracetamol")); + Assertions.assertTrue(output.contains("Take after meals")); + Assertions.assertTrue(output.contains("Prescription HTML file generated")); + } catch (UnloadedStorageException e) { + Assertions.fail("Should not throw exception for an existing prescription: " + e.getMessage()); + } + } + + @Test + public void execute_nonExistentPrescription_showsError() { + // Create command for non-existent prescription + ViewPrescriptionCommand command = new ViewPrescriptionCommand("NONEXISTENT-ID"); + + try { + // Execute command + command.execute(system, ui); + + // Verify command output indicates error + String output = outputStream.toString(); + Assertions.assertTrue(output.contains("Prescription with ID NONEXISTENT-ID not found")); + } catch (UnloadedStorageException e) { + Assertions.fail("Should handle prescription not found gracefully: " + e.getMessage()); + } + } + + @Test + public void isExit_returnsFalse() { + // Create command + ViewPrescriptionCommand command = new ViewPrescriptionCommand("S1234567A-1"); + + // Verify isExit returns false + Assertions.assertFalse(command.isExit()); + } +} + + + diff --git a/src/test/java/manager/ManagementSystemPrescriptionTest.java b/src/test/java/manager/ManagementSystemPrescriptionTest.java new file mode 100644 index 0000000000..da3ba2cb67 --- /dev/null +++ b/src/test/java/manager/ManagementSystemPrescriptionTest.java @@ -0,0 +1,162 @@ +package manager; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; +import storage.Storage; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +//@@author Basudeb2005 +public class ManagementSystemPrescriptionTest { + + private ManagementSystem system; + private Patient testPatient; + + @BeforeEach + public void setUp() { + // Create a temporary directory for Storage + try { + // Initialize Storage with temp directory + new Storage(System.getProperty("java.io.tmpdir") + File.separator + "clinicease_test"); + + // Initialize system with empty lists + system = new ManagementSystem(new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + + // Add a test patient + testPatient = new Patient( + "S1234567A", "John Doe", "1990-01-01", "M", + "123 Main St", "98765432", new ArrayList<>() + ); + + system.addPatient(testPatient); + } catch (Exception e) { + Assertions.fail("Failed to set up test: " + e.getMessage()); + } + } + + @Test + public void addPrescription_validPrescription_success() { + // Create a prescription for an existing patient + List symptoms = Arrays.asList("Fever", "Cough"); + List medicines = Arrays.asList("Paracetamol", "Cough syrup"); + Prescription prescription = new Prescription(testPatient.getId(), symptoms, medicines, "Take after meals"); + + try { + // Add prescription + Prescription addedPrescription = system.addPrescription(prescription); + + // Verify prescription was added with correct attributes + Assertions.assertNotNull(addedPrescription); + Assertions.assertEquals(testPatient.getId() + "-1", addedPrescription.getPrescriptionId()); + Assertions.assertEquals(testPatient.getId(), addedPrescription.getPatientId()); + Assertions.assertEquals(symptoms, addedPrescription.getSymptoms()); + Assertions.assertEquals(medicines, addedPrescription.getMedicines()); + Assertions.assertEquals("Take after meals", addedPrescription.getNotes()); + + // Verify it was added to the system's list + Assertions.assertEquals(1, system.getPrescriptions().size()); + } catch (Exception e) { + Assertions.fail("Should not throw exception for a valid prescription: " + e.getMessage()); + } + } + + @Test + public void addPrescription_nonExistentPatient_throwsException() { + // Create a prescription with non-existent patient ID + List symptoms = Arrays.asList("Fever"); + List medicines = Arrays.asList("Paracetamol"); + Prescription prescription = new Prescription("NONEXISTENT", symptoms, medicines, ""); + + // Try to add prescription - should throw IllegalArgumentException + Exception exception = Assertions.assertThrows(IllegalArgumentException.class, () -> { + system.addPrescription(prescription); + }); + + Assertions.assertTrue(exception.getMessage().contains("Patient with NRIC: NONEXISTENT not found")); + } + + @Test + public void getPrescriptionsForPatient_multipleExistingPrescriptions_returnsAllPrescriptions() { + try { + // Add two prescriptions for the same patient + List symptoms1 = Arrays.asList("Fever"); + List medicines1 = Arrays.asList("Paracetamol"); + Prescription prescription1 = new Prescription(testPatient.getId(), symptoms1, medicines1, ""); + + List symptoms2 = Arrays.asList("Headache"); + List medicines2 = Arrays.asList("Ibuprofen"); + Prescription prescription2 = new Prescription(testPatient.getId(), symptoms2, medicines2, ""); + + system.addPrescription(prescription1); + system.addPrescription(prescription2); + + // Get prescriptions for patient + List results = system.getPrescriptionsForPatient(testPatient.getId()); + + // Verify both prescriptions are returned + Assertions.assertEquals(2, results.size()); + Assertions.assertTrue(results.get(0).getSymptoms().contains("Fever")); + Assertions.assertTrue(results.get(1).getSymptoms().contains("Headache")); + } catch (Exception e) { + Assertions.fail("Should not throw exception: " + e.getMessage()); + } + } + + @Test + public void getPrescriptionsForPatient_noExistingPrescriptions_returnsEmptyList() { + // Get prescriptions for patient with no prescriptions + List results = system.getPrescriptionsForPatient(testPatient.getId()); + + // Verify empty list is returned + Assertions.assertTrue(results.isEmpty()); + } + + @Test + public void getPrescriptionsForPatient_nonExistentPatient_returnsEmptyList() { + // Get prescriptions for non-existent patient ID + List results = system.getPrescriptionsForPatient("NONEXISTENT"); + + // Verify empty list is returned + Assertions.assertTrue(results.isEmpty()); + } + + @Test + public void getPrescriptionById_existingPrescription_returnsPrescription() { + try { + // Add a prescription + List symptoms = Arrays.asList("Fever"); + List medicines = Arrays.asList("Paracetamol"); + Prescription added = system.addPrescription( + new Prescription(testPatient.getId(), symptoms, medicines, "") + ); + + // Get prescription by ID + Prescription result = system.getPrescriptionById(added.getPrescriptionId()); + + // Verify prescription is returned + Assertions.assertNotNull(result); + Assertions.assertEquals(added.getPrescriptionId(), result.getPrescriptionId()); + Assertions.assertEquals(testPatient.getId(), result.getPatientId()); + Assertions.assertTrue(result.getSymptoms().contains("Fever")); + Assertions.assertTrue(result.getMedicines().contains("Paracetamol")); + } catch (Exception e) { + Assertions.fail("Should not throw exception: " + e.getMessage()); + } + } + + @Test + public void getPrescriptionById_nonExistentPrescription_returnsNull() { + // Get prescription with non-existent ID + Prescription result = system.getPrescriptionById("NON-EXISTENT-ID"); + + // Verify null is returned + Assertions.assertNull(result); + } +} + + + diff --git a/src/test/java/manager/ManagementSystemTest.java b/src/test/java/manager/ManagementSystemTest.java new file mode 100644 index 0000000000..dc093ed54f --- /dev/null +++ b/src/test/java/manager/ManagementSystemTest.java @@ -0,0 +1,473 @@ +package manager; + +import exception.PatientNotFoundException; +import exception.AppointmentClashException; +import exception.DuplicatePatientIDException; +import exception.InvalidInputFormatException; +import exception.UnloadedStorageException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import storage.Storage; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class ManagementSystemTest { + + private static final String TEST_DIR = "test-data"; + private static final Path TEST_FILE_PATH = Paths.get(TEST_DIR, "patient_data.txt"); + private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + + + @BeforeEach + void setUp() throws IOException { + new Storage(TEST_DIR); + Files.deleteIfExists(TEST_FILE_PATH); + } + + @Test + void addPatient_validInput_expectPatientAdded() throws DuplicatePatientIDException, + UnloadedStorageException, InvalidInputFormatException { + List emptyListPatient = new ArrayList<>(); + List emptyListAppoint = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(emptyListPatient, emptyListAppoint); + + Patient patient = new Patient("S1234567A", "John Doe", "1990-10-01", + "M", "123 Main St", "81234567", new ArrayList<>()); + + manager.addPatient(patient); + + assertEquals(1, manager.getPatients().size()); + assertEquals("John Doe", manager.getPatients().get(0).getName()); + } + + @Test + void addPatient_duplicateId_expectExceptionThrown() throws InvalidInputFormatException { + List existing = new ArrayList<>(); + Patient patient = new Patient("S1234567A", "John Doe", "1990-01-01", + "M", "123 Main St", "81234567", new ArrayList<>()); + existing.add(patient); + + ManagementSystem manager = new ManagementSystem(existing, new ArrayList<>()); + + Patient duplicate = new Patient("S1234567A", "Jane Smith", "1992-02-02", + "F", "456 Sample Rd", "90000000", new ArrayList<>()); + + assertThrows(DuplicatePatientIDException.class, () -> manager.addPatient(duplicate)); + } + + @Test + void addPatient_validInput_expectPatientAddedAndSaved() + throws DuplicatePatientIDException, UnloadedStorageException, IOException, InvalidInputFormatException { + List emptyListPatient = new ArrayList<>(); + List emptyListAppoint = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(emptyListPatient, emptyListAppoint); + + Patient patient = new Patient("S1234567A", "John Doe", "1990-01-01", + "M", "123 Main St", "81234567", new ArrayList<>()); + manager.addPatient(patient); + + assertTrue(Files.exists(TEST_FILE_PATH), "Patient file was not created."); + + List lines = Files.readAllLines(TEST_FILE_PATH); + assertFalse(lines.isEmpty(), "Patient file is empty."); + + String line = lines.get(0); + assertTrue(line.contains("John Doe"), "Saved patient name not found in file."); + assertTrue(line.contains("S1234567A"), "Saved patient NRIC not found in file."); + } + + @Test + void deletePatient_existingPatient_patientDeleted() throws UnloadedStorageException, InvalidInputFormatException { + List patients = new ArrayList<>(); + Patient patient = new Patient("S1234567A", "John Doe", "1990-01-01", + "M", "123 Main St", "81234567", new ArrayList<>()); + patients.add(patient); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + Patient deletedPatient = manager.deletePatient("S1234567A"); + + assertNotNull(deletedPatient, "Patient should be deleted"); + assertEquals("John Doe", deletedPatient.getName(), "Deleted patient's name should match"); + assertEquals("S1234567A", deletedPatient.getId(), "Deleted patient's NRIC should match"); + assertEquals(0, manager.getPatients().size(), "Patients list should be empty after deletion"); + } + + @Test + void deletePatient_nonExistentPatient_patientNotFound() throws + UnloadedStorageException, InvalidInputFormatException { + List patients = new ArrayList<>(); + Patient patient = new Patient("S1234567A", "John Doe", "1990-01-01", + "M", "123 Main St", "81234567", new ArrayList<>()); + patients.add(patient); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + Patient deletedPatient = manager.deletePatient("S9999999X"); + + assertNull(deletedPatient, "Patient should not be found and returned as null"); + assertEquals(1, manager.getPatients().size(), "Patients list should remain unchanged"); + } + + @Test + void deletePatient_emptyList_patientNotFound() throws UnloadedStorageException { + List patients = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + Patient deletedPatient = manager.deletePatient("S1234567A"); + + assertNull(deletedPatient, "Patient should not be found in an empty system"); + assertEquals(0, manager.getPatients().size(), "Patients list should remain empty"); + } + + @Test + void viewPatient_validNric_patientFound() throws InvalidInputFormatException { + List patients = new ArrayList<>(); + patients.add(new Patient("S1234567A", "John Doe", "1990-01-01", + "M", "123 Main St", "81234567", new ArrayList<>())); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + Patient retrievedPatient = manager.viewPatient("S1234567A"); + + assertNotNull(retrievedPatient, "Patient should be found"); + assertEquals("John Doe", retrievedPatient.getName(), "Patient name should match"); + assertEquals("S1234567A", retrievedPatient.getId(), "Patient NRIC should match"); + } + + @Test + void viewPatient_invalidNric_patientNotFound() throws InvalidInputFormatException { + List patients = new ArrayList<>(); + patients.add(new Patient("S1234567A", "John Doe", "1990-01-01", + "M", "123 Main St", "81234567", new ArrayList<>())); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + Patient retrievedPatient = manager.viewPatient("S9999999X"); + + assertNull(retrievedPatient, "Patient should not be found with this NRIC"); + } + + @Test + void viewPatient_emptySystem_patientNotFound() { + List patients = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + Patient retrievedPatient = manager.viewPatient("S1234567A"); + + assertNull(retrievedPatient, "Patient should not be found when system is empty"); + } + + @Test + void addAppointment_validInput_expectAppointmentAdded() throws UnloadedStorageException, PatientNotFoundException, + AppointmentClashException, InvalidInputFormatException { + List patients = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + LocalDateTime appointmentTime1 = LocalDateTime.parse("2025-03-20 1900", DATE_TIME_FORMAT); + LocalDateTime appointmentTime2 = LocalDateTime.parse("2025-03-22 1200", DATE_TIME_FORMAT); + + Patient patient = new Patient("S1234567D", "Billy", "1990-10-01", + "M", "124 High St", "81234567", new ArrayList<>()); + patients.add(patient); + Appointment appointment1 = new Appointment("S1234567D", appointmentTime1, "Medical Checkup"); + Appointment appointment2 = new Appointment("S1234567D", appointmentTime2, "Consultation"); + + String expectedNric = appointment1.getNric(); + manager.addAppointment(appointment1); + manager.addAppointment(appointment2); + + assertEquals(2, manager.getAppointments().size(), "Size does not match"); + assertEquals(expectedNric, manager.getAppointments().get(0).getNric(), "NRIC does not match"); + assertEquals(expectedNric, manager.getAppointments().get(1).getNric(), "NRIC does not match"); + assertEquals(LocalDate.of(2025, 3, 20), manager.getAppointments().get(0).getDate()); + assertEquals(LocalTime.of(19, 0), manager.getAppointments().get(0).getTime()); + assertEquals(LocalDate.of(2025, 3, 22), manager.getAppointments().get(1).getDate()); + assertEquals(LocalTime.of(12, 0), manager.getAppointments().get(1).getTime()); + } + + @Test + void addAppointment_clashedAppointments_expectException() throws UnloadedStorageException, PatientNotFoundException, + AppointmentClashException, InvalidInputFormatException { + List patients = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + LocalDateTime appointmentTime1 = LocalDateTime.parse("2025-03-20 1900", DATE_TIME_FORMAT); + LocalDateTime appointmentTime2 = LocalDateTime.parse("2025-03-20 1930", DATE_TIME_FORMAT); + + Patient patient = new Patient("S1234567D", "Billy", "1990-10-01", + "M", "124 High St", "81234567", new ArrayList<>()); + patients.add(patient); + Appointment appointment1 = new Appointment("S1234567D", appointmentTime1, "Medical Checkup"); + Appointment appointment2 = new Appointment("S1234567D", appointmentTime2, "X-ray"); + + manager.addAppointment(appointment1); + assertThrows(AppointmentClashException.class, () -> manager.addAppointment(appointment2)); + } + + @Test + void addAppointment_nonExistingPatient_expectException() { + List patients = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + LocalDateTime appointmentTime = LocalDateTime.parse("2025-03-20 1900", DATE_TIME_FORMAT); + + Appointment appointment = new Appointment("S1234567D", appointmentTime, "Medical Checkup"); + + assertThrows(PatientNotFoundException.class, () -> manager.addAppointment(appointment)); + } + + @Test + void deleteAppointment_validInput_expectAppointmentDeleted() throws UnloadedStorageException, + PatientNotFoundException, AppointmentClashException, InvalidInputFormatException { + List patients = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + LocalDateTime appointmentTime = LocalDateTime.parse("2025-03-20 1900", DATE_TIME_FORMAT); + + Patient patient = new Patient("S1234567D", "Billy", "1990-10-01", + "M", "124 High St", "81234567", new ArrayList<>()); + patients.add(patient); + Appointment appointment = new Appointment("S1234567D", appointmentTime, "Medical Checkup"); + + String expectedId = appointment.getId(); + manager.addAppointment(appointment); + Appointment removedAppointment = manager.deleteAppointment(expectedId); + + assertNotNull(removedAppointment, "Deleted appointment should be returned"); + assertEquals(expectedId, appointment.getId(), "Appointment ID does not match"); + assertEquals(0, manager.getAppointments().size(), "Size does not match"); + } + + @Test + void deleteAppointment_nonExistentId_expectNullReturned() throws UnloadedStorageException, + PatientNotFoundException, AppointmentClashException, InvalidInputFormatException { + List patients = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + LocalDateTime appointmentTime = LocalDateTime.parse("2025-03-25 2100", DATE_TIME_FORMAT); + + Patient patient = new Patient("S1234567D", "Billy", "1990-10-01", + "M", "124 High St", "81234567", new ArrayList<>()); + patients.add(patient); + Appointment appointment = new Appointment("S1234567D", appointmentTime, "Medical Checkup"); + + manager.addAppointment(appointment); + Appointment removedAppointment = manager.deleteAppointment("A999"); + + assertEquals(1, manager.getAppointments().size(), "Size does not match"); + assertNull(removedAppointment, "Should return null"); + } + + @Test + void sortAppointmentsByDateTime_sortByDateTime_appointmentsSortedByDateTime() { + ManagementSystem manager = new ManagementSystem(new ArrayList<>(), new ArrayList<>()); + List appointments = new ArrayList<>(); + + LocalDateTime appointmentTime1 = LocalDateTime.parse("2025-03-25 1900", DATE_TIME_FORMAT); + LocalDateTime appointmentTime2 = LocalDateTime.parse("2025-03-24 1200", DATE_TIME_FORMAT); + LocalDateTime appointmentTime3 = LocalDateTime.parse("2025-03-25 1000", DATE_TIME_FORMAT); + + Appointment appointment1 = new Appointment("S1234567D", appointmentTime1, "Checkup"); + appointments.add(appointment1); + + Appointment appointment2 = new Appointment("S4567890D", appointmentTime2, "CT scan"); + appointments.add(appointment2); + + Appointment appointment3 = new Appointment("S7891234D", appointmentTime3, "Consultation"); + appointments.add(appointment3); + + manager.sortAppointmentsByDateTime(appointments); + + assertEquals("CT scan", appointments.get(0).getDescription()); + assertEquals("Consultation", appointments.get(1).getDescription()); + assertEquals("Checkup", appointments.get(2).getDescription()); + } + + @Test + void sortAppointmentsById_sortById_appointmentsSortedById() { + ManagementSystem manager = new ManagementSystem(new ArrayList<>(), new ArrayList<>()); + List appointments = new ArrayList<>(); + + LocalDateTime appointmentTime1 = LocalDateTime.parse("2025-03-25 1900", DATE_TIME_FORMAT); + LocalDateTime appointmentTime2 = LocalDateTime.parse("2025-03-24 1200", DATE_TIME_FORMAT); + LocalDateTime appointmentTime3 = LocalDateTime.parse("2025-03-25 1000", DATE_TIME_FORMAT); + + Appointment appointment1 = new Appointment("S1234567D", appointmentTime1, "Checkup"); + appointments.add(appointment1); + + Appointment appointment2 = new Appointment("S4567890D", appointmentTime2, "CT scan"); + appointments.add(appointment2); + + Appointment appointment3 = new Appointment("S7891234D", appointmentTime3, "Consultation"); + appointments.add(appointment3); + + manager.sortAppointmentsByDateTime(appointments); + manager.sortAppointmentsById(appointments); + + assertEquals("Checkup", appointments.get(0).getDescription()); + assertEquals("CT scan", appointments.get(1).getDescription()); + assertEquals("Consultation", appointments.get(2).getDescription()); + } + + //@@author dylancmznus + @Test + void markAppointment_validInput_expectAppointmentMarked() throws DuplicatePatientIDException, + UnloadedStorageException, PatientNotFoundException, AppointmentClashException, InvalidInputFormatException { + ManagementSystem manager = new ManagementSystem(new ArrayList<>(), new ArrayList<>()); + + Patient patient = new Patient("S9876543Z", "John Doe", "1990-01-01", + "M", "123 Street", "12345678", new ArrayList<>()); + manager.addPatient(patient); + + LocalDateTime appointmentTime = LocalDateTime.parse("2025-04-10 1500", DATE_TIME_FORMAT); + Appointment appointment = new Appointment("S9876543Z", appointmentTime, "Dental Checkup"); + + manager.addAppointment(appointment); + manager.markAppointment(appointment.getId()); + + assertTrue(manager.getAppointments().get(0).isDone(), "Appointment should be marked"); + } + + //@@author dylancmznus + @Test + void unmarkAppointment_validInput_expectAppointmentUnmarked() throws DuplicatePatientIDException, + UnloadedStorageException, PatientNotFoundException, AppointmentClashException, InvalidInputFormatException { + ManagementSystem manager = new ManagementSystem(new ArrayList<>(), new ArrayList<>()); + + Patient patient = new Patient("S8765432Y", "John Doe", "1990-01-01", + "M", "123 Street", "12345678", new ArrayList<>()); + manager.addPatient(patient); + + LocalDateTime appointmentTime = LocalDateTime.parse("2025-05-15 1030", DATE_TIME_FORMAT); + Appointment appointment = new Appointment("S8765432Y", appointmentTime, "Eye Examination"); + + manager.addAppointment(appointment); + manager.markAppointment(appointment.getId()); + manager.unmarkAppointment(appointment.getId()); + + assertFalse(manager.getAppointments().get(0).isDone(), "Appointment should be unmarked"); + } + + //@@author dylancmznus + @Test + void findAppointment_existingAppointment_expectAppointmentFound() throws DuplicatePatientIDException, + UnloadedStorageException, PatientNotFoundException, AppointmentClashException, InvalidInputFormatException { + ManagementSystem manager = new ManagementSystem(new ArrayList<>(), new ArrayList<>()); + + Patient patient = new Patient("S7654321X", "John Doe", "1990-01-01", + "M", "123 Street", "12345678", new ArrayList<>()); + manager.addPatient(patient); + + LocalDateTime appointmentTime = LocalDateTime.parse("2025-06-20 0900", DATE_TIME_FORMAT); + Appointment appointment = new Appointment("S7654321X", appointmentTime, "General Consultation"); + + manager.addAppointment(appointment); + List foundAppointments = manager.findAppointmentsByNric(appointment.getNric()); + + assertNotNull(foundAppointments, "Appointment should be found"); + assertEquals(appointment.getId(), foundAppointments.get(0).getId(), "Appointment ID should match"); + } + + //@@author dylancmznus + @Test + void findAppointment_nonExistentAppointment_expectNullReturned() { + List emptyListPatient = new ArrayList<>(); + List emptyListAppoint = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(emptyListPatient, emptyListAppoint); + + List foundAppointments = manager.findAppointmentsByNric("A999"); + + assertTrue(foundAppointments.isEmpty(), "Non-existent appointment should return empty list"); + } + + + //@@author jyukuan + @Test + void storeMedicalHistory_storeMedHistoryOnNewPatient_expectOneNewPatientWithMedHistory() + throws UnloadedStorageException, PatientNotFoundException, InvalidInputFormatException { + List patients = new ArrayList<>(); + ManagementSystem mhm = new ManagementSystem(patients, new ArrayList<>()); + + LocalDateTime appointmentTime = LocalDateTime.parse("2025-03-20 1900", DATE_TIME_FORMAT); + + Patient patient = new Patient("S1234567A", "John Doe", "1990-10-01", + "M", "124 High St", "81234567", new ArrayList<>()); + patients.add(patient); + + mhm.storeMedicalHistory("S1234567A", "Diabetes, Hypertension"); + + assertEquals(1, patients.size(), "There should be one patient stored"); + + Patient storedPatient = patients.get(0); + assertEquals("S1234567A", storedPatient.getId(), "Patient NRIC should match"); + + List history = storedPatient.getMedicalHistory(); + assertEquals(2, history.size(), "Medical history should contain 2 entries"); + assertTrue(history.contains("Diabetes"), "Medical history should contain 'Diabetes'"); + assertTrue(history.contains("Hypertension"), "Medical history should contain 'Hypertension'"); + } + + @Test + void editPatientHistory_oldEntryNotFound_expectNoChange() throws + UnloadedStorageException, InvalidInputFormatException { + List patients = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + + List history = new ArrayList<>(List.of("Cold", "Migraine")); + Patient patient = new Patient("F8888888Q", "Ellen", "1970-12-12", "F", "99 Peace Ave", "85556666", history); + patients.add(patient); + + manager.editPatientHistory("F8888888Q", "Cancer", "Diabetes"); + + List updatedHistory = patient.getMedicalHistory(); + + assertEquals(2, updatedHistory.size(), "Unexpected change in history size"); + assertFalse(updatedHistory.contains("Diabetes"), "Incorrectly added new history"); + assertTrue(updatedHistory.contains("Cold"), "Valid entry unexpectedly removed"); + assertTrue(updatedHistory.contains("Migraine"), "Valid entry unexpectedly removed"); + } + + @Test + void editPatientHistory_validHistory_expectUpdated() throws UnloadedStorageException, InvalidInputFormatException { + List patients = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + List history = new ArrayList<>(List.of("High BP", "Migraine")); + Patient patient = new Patient("F1234567X", "Carol", "1975-03-15", "F", "Blk 999", "83334444", history); + patients.add(patient); + + manager.editPatientHistory("F1234567X", "High BP", "Hypertension"); + + List updatedHistory = patient.getMedicalHistory(); + assertTrue(updatedHistory.contains("Hypertension"), "Replacement failed"); + assertFalse(updatedHistory.contains("High BP"), "Old entry not removed"); + } + + @Test + void editPatientHistory_emptyNewHistory_expectAssertionError() throws InvalidInputFormatException { + // Setup + List patients = new ArrayList<>(); + ManagementSystem manager = new ManagementSystem(patients, new ArrayList<>()); + List history = new ArrayList<>(List.of("Headache")); + Patient patient = new Patient("T7654321B", "Sarah", "1992-03-03", + "F", "88 Health Ave", "81231234", history); + patients.add(patient); + + // Verify assertion + assertThrows(AssertionError.class, + () -> manager.editPatientHistory("T7654321B", "Headache", "")); + } + +} diff --git a/src/test/java/manager/PrescriptionTest.java b/src/test/java/manager/PrescriptionTest.java new file mode 100644 index 0000000000..bae62be536 --- /dev/null +++ b/src/test/java/manager/PrescriptionTest.java @@ -0,0 +1,145 @@ +package manager; + +import exception.InvalidInputFormatException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +//@@author Basudeb2005 +public class PrescriptionTest { + + @Test + public void toFileFormat_validPrescription_success() { + // Create a sample prescription + String patientId = "S1234567A"; + List symptoms = Arrays.asList("Fever", "Cough"); + List medicines = Arrays.asList("Paracetamol", "Cough syrup"); + String notes = "Take after meals"; + + LocalDateTime timestamp = LocalDateTime.now(); + String prescriptionId = patientId + "-1"; + + Prescription prescription = new Prescription( + patientId, prescriptionId, timestamp, symptoms, medicines, notes + ); + + // Convert to file format + String fileFormat = prescription.toFileFormat(); + + // Verify all required components are in the file format + Assertions.assertTrue(fileFormat.contains(prescriptionId)); + Assertions.assertTrue(fileFormat.contains(patientId)); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + String formattedTimestamp = timestamp.format(formatter); + Assertions.assertTrue(fileFormat.contains(formattedTimestamp)); + Assertions.assertTrue(fileFormat.contains("Fever,Cough")); + Assertions.assertTrue(fileFormat.contains("Paracetamol,Cough syrup")); + Assertions.assertTrue(fileFormat.contains(notes)); + } + + @Test + public void fromFileFormat_validInput_success() { + // Create a properly formatted file entry + String fileEntry = "S1234567A-1|S1234567A|2023-04-01 14:30|" + + "Fever,Cough|Paracetamol,Cough syrup|Take after meals"; + + // Parse from file format + Prescription prescription = Prescription.fromFileFormat(fileEntry); + + // Verify all fields are correctly parsed + Assertions.assertEquals("S1234567A-1", prescription.getPrescriptionId()); + Assertions.assertEquals("S1234567A", prescription.getPatientId()); + Assertions.assertEquals(2, prescription.getSymptoms().size()); + Assertions.assertTrue(prescription.getSymptoms().contains("Fever")); + Assertions.assertTrue(prescription.getSymptoms().contains("Cough")); + Assertions.assertEquals(2, prescription.getMedicines().size()); + Assertions.assertTrue(prescription.getMedicines().contains("Paracetamol")); + Assertions.assertTrue(prescription.getMedicines().contains("Cough syrup")); + Assertions.assertEquals("Take after meals", prescription.getNotes()); + } + + @Test + public void fromFileFormat_invalidInput_returnsNull() { + // Create an improperly formatted file entry (missing fields) + String invalidFileEntry = "S1234567A-1|S1234567A"; + + // With our improved implementation, this should return null instead of throwing an exception + Prescription result = Prescription.fromFileFormat(invalidFileEntry); + Assertions.assertNull(result); + } + + @Test + public void generateHtml_withPatient_containsPatientInfo() + throws InvalidInputFormatException { + + // Create a sample prescription + String patientId = "S1234567A"; + List symptoms = Arrays.asList("Fever", "Cough"); + List medicines = Arrays.asList("Paracetamol", "Cough syrup"); + String notes = "Take after meals"; + + LocalDateTime timestamp = LocalDateTime.now(); + String prescriptionId = patientId + "-1"; + + Prescription prescription = new Prescription( + patientId, prescriptionId, timestamp, symptoms, medicines, notes + ); + + // Create a sample patient + Patient patient = new Patient( + patientId, "John Doe", "1990-01-01", "M", + "123 Main St", "98765432", new ArrayList<>() + ); + + // Generate HTML + String html = prescription.generateHtml(patient); + + // Verify HTML contains both prescription and patient information + Assertions.assertTrue(html.contains("")); + Assertions.assertTrue(html.contains("Prescription " + prescriptionId + "")); + Assertions.assertTrue(html.contains("John Doe")); // Patient name + Assertions.assertTrue(html.contains("M")); // Patient gender + Assertions.assertTrue(html.contains("Fever")); // Symptom + Assertions.assertTrue(html.contains("Paracetamol")); // Medicine + Assertions.assertTrue(html.contains("Take after meals")); // Notes + } + + @Test + public void generateHtml_withoutPatient_containsOnlyPrescriptionInfo() { + // Create a sample prescription + String patientId = "S1234567A"; + List symptoms = Arrays.asList("Fever", "Cough"); + List medicines = Arrays.asList("Paracetamol", "Cough syrup"); + String notes = "Take after meals"; + + LocalDateTime timestamp = LocalDateTime.now(); + String prescriptionId = patientId + "-1"; + + Prescription prescription = new Prescription( + patientId, prescriptionId, timestamp, symptoms, medicines, notes + ); + + // Generate HTML without patient + String html = prescription.generateHtml(null); + + // Verify HTML contains prescription info but limited patient info + Assertions.assertTrue(html.contains("")); + Assertions.assertTrue(html.contains("Prescription " + prescriptionId + "")); + Assertions.assertFalse(html.contains("John Doe")); // Should not contain patient name + Assertions.assertTrue(html.contains(patientId)); // Should contain patient ID + Assertions.assertTrue(html.contains("Fever")); // Should contain symptom + Assertions.assertTrue(html.contains("Paracetamol")); // Should contain medicine + Assertions.assertTrue(html.contains("Take after meals")); // Should contain notes + } +} + + + + + diff --git a/src/test/java/miscellaneous/ParserPrescriptionTest.java b/src/test/java/miscellaneous/ParserPrescriptionTest.java new file mode 100644 index 0000000000..e044cb808a --- /dev/null +++ b/src/test/java/miscellaneous/ParserPrescriptionTest.java @@ -0,0 +1,121 @@ +package miscellaneous; + +import exception.InvalidInputFormatException; +import manager.Prescription; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; + +import java.util.List; + +//@@author Basudeb2005 +public class ParserPrescriptionTest { + + @Test + public void parseAddPrescription_validInput_success() throws InvalidInputFormatException { + // Valid input with all fields including optional notes + String input = "add-prescription ic/S1234567A s/Fever, Cough m/Paracetamol, Cough syrup nt/Take after meals"; + + Prescription prescription = Parser.parseAddPrescription(input); + + // Verify all fields are correctly parsed + Assertions.assertEquals("S1234567A", prescription.getPatientId()); + List symptoms = prescription.getSymptoms(); + Assertions.assertEquals(2, symptoms.size()); + Assertions.assertTrue(symptoms.contains("Fever")); + Assertions.assertTrue(symptoms.contains("Cough")); + + List medicines = prescription.getMedicines(); + Assertions.assertEquals(2, medicines.size()); + Assertions.assertTrue(medicines.contains("Paracetamol")); + Assertions.assertTrue(medicines.contains("Cough syrup")); + + Assertions.assertEquals("Take after meals", prescription.getNotes()); + } + + @Test + public void parseAddPrescription_missingPatientId_throwsException() { + // Missing the required ic/ parameter + String input = "add-prescription s/Fever m/Paracetamol nt/Take after meals"; + + Assertions.assertThrows(InvalidInputFormatException.class, () -> { + Parser.parseAddPrescription(input); + }); + } + + @Test + public void parseAddPrescription_missingSymptoms_throwsException() { + // Missing the required s/ parameter + String input = "add-prescription ic/S1234567A m/Paracetamol nt/Take after meals"; + + Assertions.assertThrows(InvalidInputFormatException.class, () -> { + Parser.parseAddPrescription(input); + }); + } + + @Test + public void parseAddPrescription_missingMedicines_throwsException() { + // Missing the required m/ parameter + String input = "add-prescription ic/S1234567A s/Fever nt/Take after meals"; + + Assertions.assertThrows(InvalidInputFormatException.class, () -> { + Parser.parseAddPrescription(input); + }); + } + + @Test + public void parseAddPrescription_noOptionalNotes_success() throws InvalidInputFormatException { + // Valid input with only required fields + String input = "add-prescription ic/S1234567A s/Fever m/Paracetamol"; + + Prescription prescription = Parser.parseAddPrescription(input); + + // Verify required fields are parsed and optional field is empty + Assertions.assertEquals("S1234567A", prescription.getPatientId()); + Assertions.assertEquals(1, prescription.getSymptoms().size()); + Assertions.assertEquals(1, prescription.getMedicines().size()); + Assertions.assertEquals("", prescription.getNotes()); + } + + @Test + public void parseViewAllPrescriptions_validInput_success() throws InvalidInputFormatException { + // Valid command format + String input = "view-all-prescriptions S1234567A"; + + String patientId = Parser.parseViewAllPrescriptions(input); + + Assertions.assertEquals("S1234567A", patientId); + } + + @Test + public void parseViewAllPrescriptions_missingPatientId_throwsException() { + // Command without patient ID - needs to be shorter than 22 characters + String input = "view-all-prescriptions"; + + Assertions.assertThrows(InvalidInputFormatException.class, () -> { + Parser.parseViewAllPrescriptions(input); + }); + } + + @Test + public void parseViewPrescription_validInput_success() throws InvalidInputFormatException { + // Valid command format + String input = "view-prescription S1234567A-1"; + + String prescriptionId = Parser.parseViewPrescription(input); + + Assertions.assertEquals("S1234567A-1", prescriptionId); + } + + @Test + public void parseViewPrescription_missingPrescriptionId_throwsException() { + // Command without prescription ID - needs to be shorter than 17 characters + String input = "view-prescription"; + + Assertions.assertThrows(InvalidInputFormatException.class, () -> { + Parser.parseViewPrescription(input); + }); + } +} + + + diff --git a/src/test/java/miscellaneous/ParserTest.java b/src/test/java/miscellaneous/ParserTest.java new file mode 100644 index 0000000000..3020344293 --- /dev/null +++ b/src/test/java/miscellaneous/ParserTest.java @@ -0,0 +1,250 @@ +package miscellaneous; + +import command.AddAppointmentCommand; +import command.Command; +import command.DeleteAppointmentCommand; +import command.ExitCommand; +import command.HelpCommand; +import command.ListAppointmentCommand; +import command.SortAppointmentCommand; +import exception.InvalidInputFormatException; +import exception.UnknownCommandException; +import manager.Appointment; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.Test; + +import static manager.Appointment.INPUT_FORMAT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ParserTest { + + private Appointment extractAppointment(String input) throws InvalidInputFormatException, + UnknownCommandException { + Command command = Parser.parse(input); + assertInstanceOf(AddAppointmentCommand.class, command); + return ((AddAppointmentCommand) command).getAppointment(); + } + + private String extractAppointmentId(String input) throws InvalidInputFormatException, + UnknownCommandException { + Command command = Parser.parse(input); + assertInstanceOf(DeleteAppointmentCommand.class, command); + return ((DeleteAppointmentCommand) command).getApptId(); + } + + @Test + void parse_validInputToAddAppointment_returnCorrectAddAppointmentCommand() throws Exception { + String input = "add-appointment ic/S1234567D dt/2025-09-20 t/1430 dsc/Checkup"; + + Appointment appointment = extractAppointment(input); + + assertNotNull(appointment); + assertEquals("S1234567D", appointment.getNric()); + assertEquals(LocalDateTime.parse("2025-09-20 1430", INPUT_FORMAT), appointment.getDateTime()); + assertEquals("Checkup", appointment.getDescription()); + } + + @Test + void parse_dateTimeInThePastOfAddAppointment_expectException() { + String input = "add-appointment ic/S1234567D dt/2025-03-20 t/1300 dsc/Checkup"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(input)); + } + + @Test + void parse_invalidIcFormatToAddAppointment_expectException() { + String input1 = "add-appointment ic/S1234567 dt/2025-03-20 t/1300 dsc/Checkup"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(input1)); + + String input2 = "add-appointment ic/S123467D dt/2025-03-20 t/1300 dsc/Checkup"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(input2)); + + String input3 = "add-appointment ic/123467D dt/2025-03-20 t/1300 dsc/Checkup"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(input3)); + + String input4 = "add-appointment ic/123467 dt/2025-03-20 t/1300 dsc/Checkup"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(input4)); + } + + @Test + void parse_invalidDateTimeFormat_expectException() { + String input1 = "add-appointment ic/S1234567D dt/03-19 t/1900 dsc/Checkup"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(input1)); + + String input2 = "add-appointment ic/S1234567D dt/2025-03-20 t/7:00PM dsc/Checkup"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(input2)); + } + + @Test + void parse_validInputToDeleteAppointment_expectSuccess() throws InvalidInputFormatException, + UnknownCommandException { + String input = "delete-appointment A100"; + String result = extractAppointmentId(input); + + assertEquals("A100", result, "Appointment ID does not match"); + } + + @Test + void parse_extraSpacesToDeleteAppointment_expectSuccess() throws InvalidInputFormatException, + UnknownCommandException { + String input = "delete-appointment A100"; + String result = extractAppointmentId(input); + + assertEquals("A100", result, "Appointment ID does not match"); + } + + @Test + void parse_invalidAppointmentIdToDeleteAppointment_expectException() { + String input = "delete-appointment 100"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(input)); + } + + @Test + void parse_lowercaseInputToDeleteAppointment_expectSuccess() throws InvalidInputFormatException, + UnknownCommandException { + String input = "delete-appointment a100"; + String result = extractAppointmentId(input); + + assertEquals("a100", result, "Appointment ID does not match"); + } + + @Test + void parseAddPatient_invalidInputFormat_expectException() { + String input = "add-patient n/John Doe ic/ dob/1999-12-12 g/M " + + "p/98765432 a/123 Main Street h/Diabetes, Hypertension"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(input)); + } + + @Test + void parseViewHistory_validNric() throws InvalidInputFormatException { + String[] result = Parser.parseViewHistory("view-history S1234567D"); + assertEquals("ic", result[0]); + assertEquals("S1234567D", result[1]); + } + + @Test + void parseViewHistory_validName() throws InvalidInputFormatException { + String[] result = Parser.parseViewHistory("view-history John Doe"); + assertEquals("n", result[0]); + assertEquals("John Doe", result[1]); + } + + @Test + void parseViewHistory_explicitNricPrefix() throws InvalidInputFormatException { + String[] result = Parser.parseViewHistory("view-history ic/S1234567A"); + assertEquals("ic", result[0]); + assertEquals("S1234567A", result[1]); + } + + @Test + void parseViewHistory_invalidInput_expectException() { + assertThrows(InvalidInputFormatException.class, + () -> Parser.parseViewHistory("view-history")); + assertThrows(InvalidInputFormatException.class, + () -> Parser.parseViewHistory("view-history ")); + } + + @Test + void parseStoreHistory_validInput_expectSuccess() throws InvalidInputFormatException { + String[] result = Parser.parseStoreHistory("store-history ic/S1234567D h/Allergic to nuts"); + assertEquals("S1234567D", result[0]); + assertEquals("Allergic to nuts", result[1]); + } + + @Test + void parseStoreHistory_missingFields_expectException() { + assertThrows(InvalidInputFormatException.class, + () -> Parser.parseStoreHistory("store-history ic/S1234567D")); + assertThrows(InvalidInputFormatException.class, + () -> Parser.parseStoreHistory("store-history h/Allergic to nuts")); + } + + @Test + void parse_addAppointmentCommand_expectAddAppointmentCommand() throws InvalidInputFormatException, + UnknownCommandException { + Command command = Parser.parse("add-appointment ic/S1234567D dt/2025-09-20 t/1900 dsc/Checkup"); + assertInstanceOf(AddAppointmentCommand.class, command); + } + + @Test + void parse_wrongFormatAddAppointment_expectInvalidInputFormatException() { + String unfilledParams = "add-appointment ic/ dt/ t/ dsc/"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(unfilledParams)); + + String missingIc = "add-appointment ic/ dt/2025-03-19 t/1200 dsc/medical check-up"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(missingIc)); + + String missingDate = "add-appointment ic/S1234567D dt/ t/1200 dsc/medical check-up"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(missingDate)); + + String missingTime = "add-appointment ic/S1234567D dt/2025-03-19 t/ dsc/medical check-up"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(missingTime)); + + String missingDescription = "add-appointment ic/S1234567D dt/2025-03-19 t/1200 dsc/"; + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(missingDescription)); + } + + @Test + void parse_deleteAppointmentCommand_expectDeleteAppointmentCommand() throws InvalidInputFormatException, + UnknownCommandException { + Command command = Parser.parse("delete-appointment A100"); + assertInstanceOf(DeleteAppointmentCommand.class, command); + } + + @Test + void parse_listAppointmentCommand_expectListAppointmentCommand() throws Exception { + Command command = Parser.parse("list-appointment"); + assertInstanceOf(ListAppointmentCommand.class, command); + } + + @Test + void parse_unknownCommand_expectUnknownCommandException() { + String userInput = "bee-boo"; + assertThrows(UnknownCommandException.class, () -> Parser.parse(userInput)); + } + + @Test + void parse_sortAppointmentCommand_expectSortAppointmentCommand() throws InvalidInputFormatException, + UnknownCommandException { + Command byDate = Parser.parse("sort-appointment byDate"); + assertInstanceOf(SortAppointmentCommand.class, byDate); + + Command byId = Parser.parse("sort-appointment byId"); + assertInstanceOf(SortAppointmentCommand.class, byId); + } + + @Test + void parse_invalidSortAppointmentCommand_expectException() { + assertThrows(InvalidInputFormatException.class, () -> Parser.parse("sort-appointment")); + } + + @Test + void parse_byeCommand_expectByeCommand() throws InvalidInputFormatException, + UnknownCommandException { + Command command = Parser.parse("bye"); + assertInstanceOf(ExitCommand.class, command); + + } + + @Test + void parse_helpCommand_expectHelpCommand() throws InvalidInputFormatException, + UnknownCommandException { + Command command = Parser.parse("help"); + assertInstanceOf(HelpCommand.class, command); + } + + @Test + void parse_nullInput_expectException() { + assertThrows(InvalidInputFormatException.class, () -> Parser.parse(null)); + } + + @Test + void parse_emptyInput_expectException() { + assertThrows(InvalidInputFormatException.class, () -> Parser.parse("")); + } + +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/DukeTest.java deleted file mode 100644 index 2dda5fd651..0000000000 --- a/src/test/java/seedu/duke/DukeTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package seedu.duke; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -class DukeTest { - @Test - public void sampleTest() { - assertTrue(true); - } -} diff --git a/test-data/appointment_data.txt b/test-data/appointment_data.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..5cbd60b33b 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,4 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling +==================================================== +Welcome to Clinic Management v1! +Type a command, or 'bye' to exit. +==================================================== \ No newline at end of file diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..e69de29bb2 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +0,0 @@ -James Gosling \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 25ac7a2989..1824035146 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -3,17 +3,12 @@ setlocal enableextensions pushd %~dp0 cd .. -call gradlew clean shadowJar - -cd build\libs -for /f "tokens=*" %%a in ( - 'dir /b *.jar' -) do ( - set jarloc=%%a +call gradlew clean test + +if %errorlevel% equ 0 ( + echo All tests passed! + exit /b 0 +) else ( + echo Some tests failed! + exit /b 1 ) - -java -jar %jarloc% < ..\..\text-ui-test\input.txt > ..\..\text-ui-test\ACTUAL.TXT - -cd ..\..\text-ui-test - -FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed! diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 1dcbd12021..0b754c31fe 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -1,23 +1,17 @@ #!/usr/bin/env bash -# change to script directory +# Change to script directory cd "${0%/*}" - cd .. -./gradlew clean shadowJar - -cd text-ui-test -java -jar $(find ../build/libs/ -mindepth 1 -print -quit) < input.txt > ACTUAL.TXT +# Run Gradle tests +./gradlew clean test -cp EXPECTED.TXT EXPECTED-UNIX.TXT -dos2unix EXPECTED-UNIX.TXT ACTUAL.TXT -diff EXPECTED-UNIX.TXT ACTUAL.TXT -if [ $? -eq 0 ] -then - echo "Test passed!" +# Check test results +if [ $? -eq 0 ]; then + echo "All tests passed!" exit 0 else - echo "Test failed!" + echo "Some tests failed!" exit 1 fi